Compare commits

...

226 Commits

Author SHA1 Message Date
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
ad279c6683 Translation. 2022-09-27 16:57:13 +02:00
5f0817ddb7 Formating issue. 2022-09-27 16:54:57 +02:00
7124cbcacd Fixed issue with drone failing silently.
Analyzation is now detached.
2022-09-27 16:53:18 +02:00
2a710a1a08 Translations. 2022-09-27 16:50:06 +02:00
82ec2445a1 Merge pull request 'chore/theme-cleaning' (#69) from chore/theme-cleaning into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/69
2022-09-27 14:37:23 +00:00
cabb6d494d Cleaning. 2022-09-27 15:09:33 +02:00
5c12481813 Article viewer theming. 2022-09-27 12:36:01 +02:00
b16f86dda1 All theme issues should be resolved. 2022-09-27 12:02:59 +02:00
2bc28db2cc Removed chrome custom tabs. 2022-09-27 11:23:16 +02:00
bf1b680b4a Daark theme working. 2022-09-27 10:21:42 +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
d81fb79b4f Merge pull request 'enhancement/too-many-calls' (#68) from enhancement/too-many-calls into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/68
2022-09-26 18:26:22 +00:00
144067d5b6 This helps a lot. 2022-09-26 16:16:40 +02:00
8106faa45c Only fetch intems if item caching is enabled. 2022-09-26 16:05:07 +02:00
d9ef301e0f Warning. 2022-09-26 15:58:05 +02:00
90b52232ab Updating ktor and changing the engine. This speeds things a little bit too. 2022-09-26 15:54:11 +02:00
0f000ea359 Only sort if the data is from the DB. This speeds things a little bit. 2022-09-26 15:53:39 +02:00
fb8f81a4c8 One call less. 2022-09-26 14:05:57 +02:00
a76b3dd2a9 Merge pull request 'Theme refresh' (#65) from feature/55 into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/65
2022-09-26 06:54:28 +00:00
91aed5a777 Hacky way to fix #55. 2022-09-25 22:19:47 +02:00
366b2e10f1 Adjust tests to changes in the data models 2022-09-25 22:02:25 +02:00
8823cc6c6c Cleaning. 2022-09-25 21:39:57 +02:00
d2436bb976 Merge branch 'master' into repository_tests
# Conflicts:
#	.drone.yml
2022-09-25 20:24:46 +02:00
74ef4da15b Merge pull request '2.18 serialization issues fix. Fixes #61.' (#64) from bugfix/old-version-serialization into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/64
2022-09-24 20:16:39 +00:00
bd96c67788 2.18 serialization issues fix. Fixes #61. 2022-09-24 21:55:25 +02:00
da71de6806 Merge pull request 'bugfix/ktor-404' (#62) from bugfix/ktor-404 into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/62
2022-09-24 14:55:44 +00:00
0264da8ccc Fixing #54. 2022-09-24 15:00:33 +02:00
270d959ee0 Cleaning. 2022-09-24 14:55:06 +02:00
6d11dfb80c Fixing #59. 2022-09-24 14:43:24 +02:00
4184bbb900 Update gradle. 2022-09-24 13:54:48 +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
4c12c9d570 added badge. 2022-09-14 14:54:13 +02:00
f4db02521d drone-sign (#57)
## Types of changes

- [ ] I have read the **CONTRIBUTING** document.
- [ ] My code follows the code style of this project.
- [ ] I have updated the documentation accordingly.
- [ ] I have added tests to cover my changes.
- [ ] All new and existing tests passed.
- [ ] This is **NOT** translation related.

This closes issue #XXX

This is implements feature #YYY

This finishes chore #ZZZ

Co-authored-by: aminecmi <aminecmi@gmail.com>
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/57
2022-09-14 10:32:00 +00:00
01763556b1 Fixed release build issues. 2022-09-13 09:00:54 +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
e2411c00d8 Shorter description. 2022-09-08 19:00:19 +02:00
0473a5f7bc Changelog. 2022-09-07 21:43:46 +02:00
d0d6a4378c Trying to fix fdroid build. 2022-09-07 21:24:38 +02:00
1dfa3c9f07 Version scripts. 2022-09-07 20:24:19 +02:00
815f00e764 Merge pull request 'Correctly handle the back button in settings' (#48) from davidoskky/ReaderForSelfoss-multiplatform:Settings_back into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/48
2022-09-06 18:20:38 +00:00
bdc77ab8ef Remove unused lambda 2022-09-06 12:36:37 +02:00
6bd06cb458 Correctly handle the back button in settings 2022-09-06 12:28:31 +02:00
e9e8bee6c9 Disable all the backups. 2022-08-29 15:37:43 +02:00
ff6038dbd4 Removing the backup function for security reasons. 2022-08-28 13:41:48 +02:00
8146cff011 Merge pull request 'Closes #44' (#46) from chore/cleaning_drawer_data into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/46
2022-08-28 11:41:16 +00:00
fc4c48dd12 Closes #44 2022-08-28 10:30:03 +02:00
94f1ec943c Cleaning. 2022-08-27 15:09:25 +02:00
0278540fb2 Merge pull request 'feature/api_timeout_and_settings_cleaning' (#45) from feature/api_timeout_and_settings_cleaning into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/45
2022-08-27 12:20:54 +00:00
109050d0cf Fixing a todo. 2022-08-27 13:06:16 +02:00
0392e1b406 Typo fix. 2022-08-27 13:00:19 +02:00
8b2be5c55c Refreshing settings. 2022-08-26 23:39:30 +02:00
e6afb4702d Cleaning code. 2022-08-26 23:37:02 +02:00
8cf462a755 Fixing api version issue on login. 2022-08-26 23:37:02 +02:00
17293e5574 Removing self signed certificate (Closes #43) and removing http login (Closes #37) 2022-08-26 23:37:02 +02:00
a99286a9b7 Really big settings cleaning. 2022-08-26 23:37:02 +02:00
5531034086 WIP: Big settings cleaning. 2022-08-26 23:37:01 +02:00
fbcb428e96 Merge pull request 'Fetch api version on login' (#39) from davidoskky/ReaderForSelfoss-multiplatform:loginApi into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/39
2022-08-25 02:25:27 +00:00
e281751bb0 Fetch api version on login 2022-08-24 23:26:49 +02:00
0eed9a8d07 Translations. 2022-08-24 14:41:57 +02:00
9603860bae Merge pull request 'chore/sonarqube-fixes' (#35) from chore/sonarqube-fixes into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/35
2022-08-24 12:37:37 +00:00
75b566a38d Some cleaning to handle actions on connection restore. 2022-08-24 13:58:24 +02:00
fb572dbb27 Closes #36 2022-08-24 13:34:05 +02:00
34028949d7 More code cleaning. 2022-08-23 22:52:36 +02:00
44a0469b17 Fixes #25; 2022-08-23 22:46:48 +02:00
c87473e8f1 Cache from settings. 2022-08-23 22:45:19 +02:00
de43abf019 Fixed mercury issues. 2022-08-23 22:45:19 +02:00
e60f3a9d91 Push everywhere ? 2022-08-23 22:45:19 +02:00
255fbcb12f Added build cache. 2022-08-23 22:45:19 +02:00
0caeb94e64 Translations. 2022-08-23 22:43:02 +02:00
6f6a42b878 Merge pull request 'DB caching.' (#33) from feature/sqldelight into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/33
2022-08-23 20:39:19 +00:00
67d54f0dd7 Big code cleaning. 2022-08-23 22:16:54 +02:00
437aa0abec Big code cleaning. 2022-08-23 22:03:33 +02:00
216c639a23 Closes #23 2022-08-23 20:47:07 +02:00
7258452625 Closes #23 2022-08-23 20:38:53 +02:00
d0d82751e2 Filtering DB items. 2022-08-23 20:34:20 +02:00
3b8f4991e9 Inserting items in the DB. 2022-08-23 16:56:04 +02:00
2547ce824a Repository should be DB ok. 2022-08-23 16:19:24 +02:00
59eb399cfa File renaming. 2022-08-23 15:17:47 +02:00
495b101355 Replacing room with sqldelight. Big cleaning. 2022-08-23 15:12:01 +02:00
afcc55e907 Urls. 2022-08-22 22:07:52 +02:00
0c570efc47 Urls. 2022-08-22 22:04:07 +02:00
a23a4cea0e Translation. 2022-08-22 21:58:15 +02:00
78cb5d047f Translations. 2022-08-22 21:53:31 +02:00
a9caaefb4d Merge pull request 'network' (#28) from davidoskky/ReaderForSelfoss-multiplatform:network into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/28
2022-08-22 19:01:15 +00:00
4c78b22614 Use the connectivity-status library from the repository rather than the local copy 2022-08-22 19:33:58 +02:00
1d5ab3205e Localize strings 2022-08-21 23:34:47 +02:00
df4903cae5 Include the connectivity status library as a aar file 2022-08-21 23:34:47 +02:00
2a78be69b5 Send toast messages at the application level and not on a per activity basis 2022-08-21 23:34:45 +02:00
8c69bb8c3c Send a message regarding connectivity loss/retrieval on all activities 2022-08-21 23:34:17 +02:00
9203012a97 Include a local copy of the connectivity-status library to solve a bug 2022-08-21 23:34:17 +02:00
2a44162c5a Send toast messages to the home activity on connectivity changes 2022-08-21 23:34:17 +02:00
20588aab81 Add comment to remember the problem with the connectivity-status library 2022-08-21 23:34:17 +02:00
0c8e49214f Don't reset offline override before updating remote 2022-08-21 23:34:17 +02:00
97d5063339 Consider offline override before updating remote 2022-08-21 23:34:17 +02:00
7c37b183d7 Refactor functions 2022-08-21 23:34:17 +02:00
82c4a5a1f9 Don't send toast messages from the repository 2022-08-21 23:34:17 +02:00
47b7062e16 Remove unused function 2022-08-21 23:34:17 +02:00
b9497ca939 Prepare the repository functions for DB implementation 2022-08-21 23:34:17 +02:00
1258ed3ad3 Don't create the mercury api if not connection is available 2022-08-21 23:34:17 +02:00
d838f509d4 Stop monitoring the network when the app goes in background 2022-08-21 23:34:17 +02:00
3c5b606a02 Do not change the network override from within the repository 2022-08-21 23:34:17 +02:00
d1481a1db6 Reintroduce network checks where required 2022-08-21 23:34:17 +02:00
d654b1b0bd Refactor connectivity check 2022-08-21 23:34:17 +02:00
f56861a3c2 Show a message when the network connection is lost 2022-08-21 23:34:17 +02:00
492e7e4aed Update todo comments 2022-08-21 23:34:17 +02:00
551a3e3caa Remove all connectivity checks outside the repository 2022-08-21 23:34:17 +02:00
c224b8a0b3 Remove network checks from the home activity 2022-08-21 23:34:17 +02:00
13ea7a693b Do not fake offline mode when updating remote 2022-08-21 23:34:17 +02:00
0f3c48dd8e Handle the offline override in the repository 2022-08-21 23:34:17 +02:00
d4c2373bac Simplify network connectivity status check 2022-08-21 23:34:17 +02:00
4f32097821 Perform network connectivity checks in the repository 2022-08-21 23:34:17 +02:00
37fa4a1a8e Add multiplatform connectivity check 2022-08-21 23:34:17 +02:00
112194dd4f Merge pull request 'Implement logging in the android application' (#32) from davidoskky/ReaderForSelfoss-multiplatform:logging into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/32
2022-08-20 18:18:48 +00:00
72d9ef92d2 Implement logging in the android application 2022-08-20 12:29:04 +02:00
1392e2a571 Merge pull request 'Fixing some sonarqube issues.' (#30) from chore/sonarqube-fixes into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/30
2022-08-17 19:10:47 +00:00
e9cb3d2f37 Fixing some sonarqube issues. 2022-08-17 21:00:58 +02:00
dec620a409 Merge pull request 'Changed ids to items.' (#29) from id-to-int into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/29
2022-08-17 14:31:11 +00:00
4d29ee0b92 Last fixes. 2022-08-17 16:16:11 +02:00
33333ca998 This may work. 2022-08-17 14:52:03 +02:00
8d87eef0fc More fixes. 2022-08-17 14:24:28 +02:00
5a26513ed7 These params need to be here too. 2022-08-17 14:06:56 +02:00
5b7f5225d8 Can't be detached because of a lock file. 2022-08-17 14:03:32 +02:00
03f53bf9c9 Detached scan 2022-08-17 11:04:44 +02:00
e06e6d580d Detached scan. 2022-08-17 11:00:02 +02:00
63e8649512 Fixing issues with build. 2022-08-17 10:50:04 +02:00
6260c3fc06 Fixes and drone build should work. 2022-08-17 10:43:56 +02:00
77917dd940 Merge pull request 'drone' (#26) from drone into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/26
2022-08-16 19:10:52 +00:00
171 changed files with 5082 additions and 6026 deletions

View File

@ -1,20 +1,124 @@
kind: pipeline
type: docker
name: test
steps:
- name: build
- name: AnylyseBuildTest
image: mingc/android-build-box:latest
commands:
- echo "---------------------------------------------------------"
- echo "Configure gradle..."
- mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=true\nappLoginUrl=\"URL\"\nappLoginUsername=\"LOGIN\"\nappLoginPassword=\"PASS\"\npushCache=false\nsystemProp.org.gradle.internal.http.connectionTimeout=180000\nsystemProp.org.gradle.internal.http.socketTimeout=180000" >> ~/.gradle/gradle.properties
- echo "---------------------------------------------------------"
- echo "Analysing..."
- ./gradlew sonarqube -Dsonar.projectKey=RFS2 -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN
- echo "---------------------------------------------------------"
- echo "Building..."
- ./gradlew build
- name: code-analysis
image: mingc/android-build-box:latest
failure: ignore
commands:
- ls -la
- ./gradlew sonarqube -Dsonar.projectKey=RFS2 -Dsonar.sources=. -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN
- echo "---------------------------------------------------------"
- echo "Testing..."
- echo "---------------------------------------------------------"
- ./gradlew test
environment:
SONAR_HOST_URL:
from_secret: sonarScannerHostUrl
SONAR_LOGIN:
from_secret: sonarScannerLogin
trigger:
event:
- push
- pull_request
---
kind: pipeline
type: docker
name: Publish
steps:
- name: createTag
image: ubuntu:latest
commands:
- apt-get update && apt-get install -y git
- ./build.sh --publish --from-ci
- git remote add pushing https://$GITEA_USR:$GITEA_PASS@gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform.git
- git push pushing --tags
environment:
GITEA_USR:
from_secret: giteaUsr
GITEA_PASS:
from_secret: giteaPass
- name: scpFiles
image: appleboy/drone-scp
settings:
host: amine-louveau.fr
username: ubuntu
key:
from_secret: privateKey
port: 22
target: /home/ubuntu/
source: version.txt
- name: deploy
image: appleboy/drone-ssh
settings:
host: amine-louveau.fr
user: ubuntu
key:
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/
trigger:
event:
- promote
target:
- production
---
kind: pipeline
type: docker
name: Release
steps:
- name: build
image: mingc/android-build-box:latest
commands:
- echo "---------------------------------------------------------"
- echo "Configure gradle..."
- mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=true\nappLoginUrl=\"URL\"\nappLoginUsername=\"LOGIN\"\nappLoginPassword=\"PASS\"\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 -P pushCache=false
- echo "---------------------------------------------------------"
- echo "Get Key"
- wget https://amine-louveau.fr/key
- echo "---------------------------------------------------------"
- echo "Zipalign"
- $ANDROID_HOME/build-tools/31.0.0/zipalign -f -v 4 androidApp/build/outputs/apk/githubConfig/release/androidApp-githubConfig-release-unsigned.apk androidApp/build/outputs/apk/githubConfig/release/android-prod-released-ziped.apk
- echo "---------------------------------------------------------"
- echo "Sign"
- $ANDROID_HOME/build-tools/31.0.0/apksigner sign -v --out signed.apk --ks ./key --ks-key-alias $YOUR_KEY_ALIAS --ks-pass pass:$YOUR_KEYSTORE_PASSWORD --v1-signing-enabled true --v2-signing-enabled true androidApp/build/outputs/apk/githubConfig/release/android-prod-released-ziped.apk
- echo "---------------------------------------------------------"
- echo "Verify"
- $ANDROID_HOME/build-tools/31.0.0/apksigner verify signed.apk
environment:
YOUR_KEYSTORE_PASSWORD:
from_secret: keyPass
YOUR_KEY_ALIAS:
from_secret: keyAlias
- name: gitea_release
image: plugins/gitea-release
settings:
api_key:
from_secret: giteaAPI
base_url: https://gitea.amine-louveau.fr
files: signed.apk
trigger:
event:
- tag

View File

@ -10,7 +10,7 @@ Please read the guidelines before contributing, and follow them (or try to) when
There are many ways to contribute to this project, you could [translate the app](https://crowdin.com/project/readerforselfoss), report bugs, request missing features, suggest enhancements and changes to existing ones. You also can improve the README with useful tips that could help the other users.
You can fork the repository, and [help me solve some issues](https://github.com/aminecmi/ReaderforSelfoss/issues?q=is%3Aissue+is%3Aopen+label%3A%22Up+For+Grabs%22) or [develop new things](https://github.com/aminecmi/ReaderforSelfoss/issues)
You can fork the repository, and [help me solve some issues](https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/issues?q=is%3Aissue+is%3Aopen+label%3A%22Up+For+Grabs%22) or [develop new things](https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/issues)
### What I can't help you with.
@ -28,7 +28,7 @@ Always check if the web version of your instance is working.
### Pull requests
* Don't create a PR for translations. See [here](https://github.com/aminecmi/ReaderforSelfoss/pull/170#issuecomment-355715654) for an explanation why.
* Don't create a PR for translations.
* Please ask before starting to work on an issue. I may be working on it, or someone else could be doing so.
* Each pull request should implement **ONE** feature or bugfix. Keep in mind that you can submit as many PR as you want.
* Your code must be simple and clear enough to avoid using comments to explain what it does.

View File

@ -5,7 +5,7 @@
- [ ] I have updated the documentation accordingly.
- [ ] I have added tests to cover my changes.
- [ ] All new and existing tests passed.
- [ ] This is **NOT** translation related. (See [here](https://github.com/aminecmi/ReaderforSelfoss/pull/170#issuecomment-355715654))
- [ ] This is **NOT** translation related.
This closes issue #XXX

2
.gitignore vendored
View File

@ -319,3 +319,5 @@ fabric.properties
# End of https://www.toptal.com/developers/gitignore/api/gradle,kotlin,androidstudio,android,xcode,swift
crowdin.properties

View File

@ -1,3 +1,14 @@
# V2/Multiplatform rewrite
**v1**
- The app has the same functionalities as before.
--------------------------------------------------------------------
# Old version changes
**1.7.x**
- Hiding tags with 0 articles

View File

@ -1,4 +1,4 @@
# ReaderForSelfoss-multiplatform
# ReaderForSelfoss-multiplatform [![Build Status](https://build.amine-louveau.fr/api/badges/Louvorg/ReaderForSelfoss-multiplatform/status.svg)](https://build.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform)
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/readerforselfoss/localized.svg)](https://crowdin.com/project/readerforselfoss)
@ -22,15 +22,15 @@ If you are a user, you can still create new issues. I'll fix them when I can.
1. **You'll have to have a Selfoss instance running.** You'll find everything you need to install it [here](https://selfoss.aditu.de/).
2. Check the [Contribution guide](https://github.com/aminecmi/ReaderforSelfoss-multiplatform/blob/master/.github/CONTRIBUTING.md).
2. Check the [Contribution guide](https://gitea.amine-louveau.fr/Louvorg/ReaderforSelfoss-multiplatform/src/branch/master/.github/CONTRIBUTING.md).
3. Build the project by following [these steps](https://github.com/aminecmi/ReaderforSelfoss-multiplatform/blob/master/.github/CONTRIBUTING.md#build-the-project) (you should have read them after the contribution guide)
3. Build the project by following [these steps](https://gitea.amine-louveau.fr/Louvorg/ReaderforSelfoss-multiplatform/src/branch/master/.github/CONTRIBUTING.md#build-the-project) (you should have read them after the contribution guide)
## Useful links
- [Check what changed](https://github.com/aminecmi/ReaderforSelfoss-multiplatform/blob/master/CHANGELOG.md)
- [See what I'm doing](https://github.com/aminecmi/ReaderforSelfoss-multiplatform/projects/1)
- [Create an issue, or request a new feature](https://github.com/aminecmi/ReaderforSelfoss-multiplatform/issues)
- [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)
## Contributors (V1) (Alphabetical order) ❤️

View File

@ -1,9 +1,12 @@
import java.io.ByteArrayOutputStream
val ignoreGitVersion: String by project
plugins {
id("com.android.application")
kotlin("android")
kotlin("kapt")
id("com.mikepenz.aboutlibraries.plugin")
}
fun Project.execWithOutput(cmd: String, ignore: Boolean = false): String {
@ -28,15 +31,23 @@ fun gitVersion(): String {
println("Tag found on current commit")
execWithOutput("git -C ../ describe --contains HEAD")
}
return process.replace("'", "").substring(1).replace("\\.", "").trim()
return process.replace("^0", "").replace("'", "").substring(1).replace("\\.", "").trim()
}
fun versionCodeFromGit(): Int {
if (ignoreGitVersion == "true") {
// don't care
return 1
}
println("version code " + gitVersion())
return gitVersion().toInt()
}
fun versionNameFromGit(): String {
if (ignoreGitVersion == "true") {
// don't care
return "1"
}
println("version name " + gitVersion())
return gitVersion()
}
@ -44,11 +55,15 @@ 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
// For Kotlin projects
kotlinOptions {
jvmTarget = "11"
}
compileSdk = 33
buildToolsVersion = "31.0.0"
buildFeatures {
viewBinding = true
@ -56,7 +71,7 @@ android {
defaultConfig {
applicationId = "bou.amine.apps.readerforselfossv2.android"
minSdk = 21
targetSdk = 31
targetSdk = 33
versionCode = versionCodeFromGit()
versionName = versionNameFromGit()
@ -68,11 +83,10 @@ android {
// tests
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
javaCompileOptions {
annotationProcessorOptions {
arguments += mapOf("room.schemaLocation" to "$projectDir/schemas")
}
}
packagingOptions {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
buildTypes {
@ -89,14 +103,12 @@ android {
}
flavorDimensions.add("build")
productFlavors {
create("github") {
create("githubConfig") {
versionNameSuffix = "-github"
dimension = "build"
}
}
kotlinOptions {
jvmTarget = "1.8"
}
namespace = "bou.amine.apps.readerforselfossv2.android"
}
@ -109,13 +121,6 @@ dependencies {
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
@ -124,31 +129,18 @@ dependencies {
implementation("androidx.recyclerview:recyclerview:1.3.0-alpha01")
implementation("androidx.legacy:legacy-support-v4:1.0.0")
implementation("androidx.vectordrawable:vectordrawable:1.2.0-alpha02")
implementation("androidx.browser:browser:1.4.0")
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("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")
@ -162,8 +154,6 @@ dependencies {
implementation("com.mikepenz:materialdrawer:8.4.5")
// Themes
implementation("com.52inc:scoops:1.0.0")
implementation("com.jaredrummler:colorpicker:1.1.0")
implementation("com.github.rubensousa:floatingtoolbar:1.5.1")
// Pager
@ -171,22 +161,56 @@ dependencies {
implementation("androidx.viewpager2:viewpager2:1.1.0-beta01")
//Dependency Injection
implementation("org.kodein.di:kodein-di:7.12.0")
implementation("org.kodein.di:kodein-di-framework-android-x:7.12.0")
implementation("org.kodein.di:kodein-di:7.14.0")
implementation("org.kodein.di:kodein-di-framework-android-x:7.14.0")
implementation("org.kodein.di:kodein-di-framework-android-x-viewmodel:7.14.0")
//Settings
implementation("com.russhwolf:multiplatform-settings-no-arg:0.9")
//Logging
implementation("io.github.aakira:napier:2.6.1")
//PhotoView
implementation("com.github.chrisbanes:PhotoView:2.3.0")
implementation("androidx.core:core-ktx:1.7.0")
implementation("androidx.core:core-ktx:1.8.0")
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.4.0")
implementation("androidx.lifecycle:lifecycle-common-java8:2.4.0")
implementation("androidx.lifecycle:lifecycle-extensions:2.2.0")
implementation("androidx.room:room-ktx:2.4.0-beta01")
kapt("androidx.room:room-compiler:2.4.0-beta01")
// Network information
implementation("com.github.ln-12:multiplatform-connectivity-status:1.3.0")
implementation("android.arch.work:work-runtime-ktx:1.0.1")
// SQLDELIGHT
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")
}
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
@ -55,11 +48,40 @@
public *;
}
-dontwarn com.anupcowkur.reservoir.**
-dontwarn javax.annotation.**
-keep class android.support.v7.widget.SearchView { *; }
# maybe remove later ?
-keep class * extends androidx.fragment.app.Fragment
# Keep `Companion` object fields of serializable classes.
# This avoids serializer lookup through `getDeclaredClasses` as done for named companion objects.
-if @kotlinx.serialization.Serializable class **
-keepclassmembers class <1> {
static <1>$Companion Companion;
}
# Keep `serializer()` on companion objects (both default and named) of serializable classes.
-if @kotlinx.serialization.Serializable class ** {
static **$* *;
}
-keepclassmembers class <2>$<3> {
kotlinx.serialization.KSerializer serializer(...);
}
# Keep `INSTANCE.serializer()` of serializable objects.
-if @kotlinx.serialization.Serializable class ** {
public static ** INSTANCE;
}
-keepclassmembers class <1> {
public static <1> INSTANCE;
kotlinx.serialization.KSerializer serializer(...);
}
# @Serializable and @Polymorphic are used at runtime for polymorphic serialization.
-keepattributes RuntimeVisibleAnnotations,AnnotationDefault
-dontwarn io.mockk.**
-keep class io.mockk.** { *; }

View File

@ -1,11 +0,0 @@
#!/bin/bash
# NOTE: This is copy/pasted in jenkins
rm -f version.txt
printf "versionName=$1-github\nversionCode=$1" >> version.txt
# You'll need to change server as your server and define a VERSION_PATH.
scp version.txt server:$VERSION_PATH
rm version.txt

View File

@ -1,96 +0,0 @@
{
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "08ca537d7ac9d4dd216e8e395d70801a",
"entities": [
{
"tableName": "tags",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tag` TEXT NOT NULL, `color` TEXT NOT NULL, `unread` INTEGER NOT NULL, PRIMARY KEY(`tag`))",
"fields": [
{
"fieldPath": "tag",
"columnName": "tag",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "color",
"columnName": "color",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "unread",
"columnName": "unread",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"tag"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "sources",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `title` TEXT NOT NULL, `tags` TEXT NOT NULL, `spout` TEXT NOT NULL, `error` TEXT NOT NULL, `icon` TEXT NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "tags",
"columnName": "tags",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "spout",
"columnName": "spout",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "error",
"columnName": "error",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "icon",
"columnName": "icon",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"08ca537d7ac9d4dd216e8e395d70801a\")"
]
}
}

View File

@ -1,176 +0,0 @@
{
"formatVersion": 1,
"database": {
"version": 2,
"identityHash": "6fa6944b04100d68eab61039876a8804",
"entities": [
{
"tableName": "tags",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tag` TEXT NOT NULL, `color` TEXT NOT NULL, `unread` INTEGER NOT NULL, PRIMARY KEY(`tag`))",
"fields": [
{
"fieldPath": "tag",
"columnName": "tag",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "color",
"columnName": "color",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "unread",
"columnName": "unread",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"tag"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "sources",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `title` TEXT NOT NULL, `tags` TEXT NOT NULL, `spout` TEXT NOT NULL, `error` TEXT NOT NULL, `icon` TEXT NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "tags",
"columnName": "tags",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "spout",
"columnName": "spout",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "error",
"columnName": "error",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "icon",
"columnName": "icon",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "items",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `datetime` TEXT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `thumbnail` TEXT NOT NULL, `icon` TEXT NOT NULL, `link` TEXT NOT NULL, `sourcetitle` TEXT NOT NULL, `tags` TEXT NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "datetime",
"columnName": "datetime",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "unread",
"columnName": "unread",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "starred",
"columnName": "starred",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "thumbnail",
"columnName": "thumbnail",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "icon",
"columnName": "icon",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "link",
"columnName": "link",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "sourcetitle",
"columnName": "sourcetitle",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "tags",
"columnName": "tags",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"6fa6944b04100d68eab61039876a8804\")"
]
}
}

View File

@ -1,226 +0,0 @@
{
"formatVersion": 1,
"database": {
"version": 3,
"identityHash": "7ad9c4961992c13b670128485ebb3efc",
"entities": [
{
"tableName": "tags",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tag` TEXT NOT NULL, `color` TEXT NOT NULL, `unread` INTEGER NOT NULL, PRIMARY KEY(`tag`))",
"fields": [
{
"fieldPath": "tag",
"columnName": "tag",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "color",
"columnName": "color",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "unread",
"columnName": "unread",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"tag"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "sources",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `title` TEXT NOT NULL, `tags` TEXT NOT NULL, `spout` TEXT NOT NULL, `error` TEXT NOT NULL, `icon` TEXT NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "tags",
"columnName": "tags",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "spout",
"columnName": "spout",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "error",
"columnName": "error",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "icon",
"columnName": "icon",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "items",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `datetime` TEXT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `thumbnail` TEXT NOT NULL, `icon` TEXT NOT NULL, `link` TEXT NOT NULL, `sourcetitle` TEXT NOT NULL, `tags` TEXT NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "datetime",
"columnName": "datetime",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "unread",
"columnName": "unread",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "starred",
"columnName": "starred",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "thumbnail",
"columnName": "thumbnail",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "icon",
"columnName": "icon",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "link",
"columnName": "link",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "sourcetitle",
"columnName": "sourcetitle",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "tags",
"columnName": "tags",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "actions",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `articleid` TEXT NOT NULL, `read` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `unstarred` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "articleId",
"columnName": "articleid",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "read",
"columnName": "read",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "unread",
"columnName": "unread",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "starred",
"columnName": "starred",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "unstarred",
"columnName": "unstarred",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"7ad9c4961992c13b670128485ebb3efc\")"
]
}
}

View File

@ -1,226 +0,0 @@
{
"formatVersion": 1,
"database": {
"version": 4,
"identityHash": "9cf8b03d32f80dfd58160599a1df197d",
"entities": [
{
"tableName": "tags",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tag` TEXT NOT NULL, `color` TEXT NOT NULL, `unread` INTEGER NOT NULL, PRIMARY KEY(`tag`))",
"fields": [
{
"fieldPath": "tag",
"columnName": "tag",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "color",
"columnName": "color",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "unread",
"columnName": "unread",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"tag"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "sources",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `title` TEXT NOT NULL, `tags` TEXT NOT NULL, `spout` TEXT NOT NULL, `error` TEXT NOT NULL, `icon` TEXT NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "tags",
"columnName": "tags",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "spout",
"columnName": "spout",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "error",
"columnName": "error",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "icon",
"columnName": "icon",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "items",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `datetime` TEXT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `thumbnail` TEXT, `icon` TEXT, `link` TEXT NOT NULL, `sourcetitle` TEXT NOT NULL, `tags` TEXT NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "datetime",
"columnName": "datetime",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "unread",
"columnName": "unread",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "starred",
"columnName": "starred",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "thumbnail",
"columnName": "thumbnail",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "icon",
"columnName": "icon",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "link",
"columnName": "link",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "sourcetitle",
"columnName": "sourcetitle",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "tags",
"columnName": "tags",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "actions",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `articleid` TEXT NOT NULL, `read` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `unstarred` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "articleId",
"columnName": "articleid",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "read",
"columnName": "read",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "unread",
"columnName": "unread",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "starred",
"columnName": "starred",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "unstarred",
"columnName": "unstarred",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"9cf8b03d32f80dfd58160599a1df197d\")"
]
}
}

View File

@ -1,226 +0,0 @@
{
"formatVersion": 1,
"database": {
"version": 4,
"identityHash": "9cf8b03d32f80dfd58160599a1df197d",
"entities": [
{
"tableName": "tags",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tag` TEXT NOT NULL, `color` TEXT NOT NULL, `unread` INTEGER NOT NULL, PRIMARY KEY(`tag`))",
"fields": [
{
"fieldPath": "tag",
"columnName": "tag",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "color",
"columnName": "color",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "unread",
"columnName": "unread",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"tag"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "sources",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `title` TEXT NOT NULL, `tags` TEXT NOT NULL, `spout` TEXT NOT NULL, `error` TEXT NOT NULL, `icon` TEXT NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "tags",
"columnName": "tags",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "spout",
"columnName": "spout",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "error",
"columnName": "error",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "icon",
"columnName": "icon",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "items",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `datetime` TEXT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `thumbnail` TEXT, `icon` TEXT, `link` TEXT NOT NULL, `sourcetitle` TEXT NOT NULL, `tags` TEXT NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "datetime",
"columnName": "datetime",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "unread",
"columnName": "unread",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "starred",
"columnName": "starred",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "thumbnail",
"columnName": "thumbnail",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "icon",
"columnName": "icon",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "link",
"columnName": "link",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "sourcetitle",
"columnName": "sourcetitle",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "tags",
"columnName": "tags",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "actions",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`articleid` TEXT NOT NULL, `read` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `unstarred` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
"fields": [
{
"fieldPath": "articleId",
"columnName": "articleid",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "read",
"columnName": "read",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "unread",
"columnName": "unread",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "starred",
"columnName": "starred",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "unstarred",
"columnName": "unstarred",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '9cf8b03d32f80dfd58160599a1df197d')"
]
}
}

View File

@ -1,3 +0,0 @@
package bou.amine.apps.readerforselfossv2.android
// TODO: test source adding

View File

@ -1,102 +0,0 @@
package bou.amine.apps.readerforselfossv2.android
import android.content.Context
import android.content.Intent
import androidx.test.InstrumentationRegistry
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.closeSoftKeyboard
import androidx.test.espresso.action.ViewActions.pressKey
import androidx.test.espresso.action.ViewActions.typeText
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.intent.Intents
import androidx.test.espresso.intent.Intents.intended
import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.rule.ActivityTestRule
import androidx.test.runner.AndroidJUnit4
import android.view.KeyEvent
import androidx.test.espresso.matcher.RootMatchers.isDialog
import bou.amine.apps.readerforselfossv2.android.HomeActivity
import bou.amine.apps.readerforselfossv2.android.LoginActivity
import bou.amine.apps.readerforselfossv2.android.utils.Config
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class HomeActivityEspressoTest {
lateinit var context: Context
@Rule @JvmField
val rule = ActivityTestRule(HomeActivity::class.java, true, false)
@Before
fun clearData() {
context = InstrumentationRegistry.getInstrumentation().targetContext
val editor =
context
.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
.edit()
editor.clear()
editor.putString("url", BuildConfig.LOGIN_URL)
editor.putString("login", BuildConfig.LOGIN_USERNAME)
editor.putString("password", BuildConfig.LOGIN_PASSWORD)
editor.commit()
Intents.init()
}
@Test
fun menuItems() {
rule.launchActivity(Intent())
onView(
withMenu(
id = R.id.action_search,
titleId = R.string.menu_home_search
)
).perform(click())
onView(withId(R.id.search_bar)).check(matches(isDisplayed()))
onView(withId(R.id.search_src_text)).perform(
typeText("android"),
pressKey(KeyEvent.KEYCODE_SEARCH),
closeSoftKeyboard()
)
onView(withContentDescription(R.string.abc_toolbar_collapse_description)).perform(click())
openActionBarOverflowOrOptionsMenu(context)
onView(withMenu(id = R.id.refresh, titleId = R.string.menu_home_refresh))
.perform(click())
onView(withText(android.R.string.ok))
.inRoot(isDialog()).check(matches(isDisplayed())).perform(click())
openActionBarOverflowOrOptionsMenu(context)
onView(withText(R.string.action_disconnect)).perform(click())
intended(hasComponent(LoginActivity::class.java.name))
}
// TODO: test articles opening and actions for cards and lists
@After
fun releaseIntents() {
Intents.release()
}
}

View File

@ -1,180 +0,0 @@
package bou.amine.apps.readerforselfossv2.android
import android.content.Context
import android.content.Intent
import androidx.test.InstrumentationRegistry
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.closeSoftKeyboard
import androidx.test.espresso.action.ViewActions.pressBack
import androidx.test.espresso.action.ViewActions.typeText
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.intent.Intents
import androidx.test.espresso.intent.Intents.intended
import androidx.test.espresso.intent.Intents.times
import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.isRoot
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.rule.ActivityTestRule
import androidx.test.runner.AndroidJUnit4
import bou.amine.apps.readerforselfossv2.android.HomeActivity
import bou.amine.apps.readerforselfossv2.android.LoginActivity
import bou.amine.apps.readerforselfossv2.android.utils.Config
import com.mikepenz.aboutlibraries.ui.LibsActivity
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class LoginActivityEspressoTest {
@Rule @JvmField
val rule = ActivityTestRule(LoginActivity::class.java, true, false)
private lateinit var context: Context
private lateinit var url: String
private lateinit var username: String
private lateinit var password: String
@Before
fun setUp() {
context = InstrumentationRegistry.getInstrumentation().targetContext
val editor =
context
.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
.edit()
editor.clear()
editor.commit()
url = BuildConfig.LOGIN_URL
username = BuildConfig.LOGIN_USERNAME
password = BuildConfig.LOGIN_PASSWORD
Intents.init()
}
@Test
fun menuItems() {
rule.launchActivity(Intent())
openActionBarOverflowOrOptionsMenu(context)
onView(withText(R.string.action_about)).perform(click())
intended(hasComponent(LibsActivity::class.java.name), times(1))
onView(isRoot()).perform(pressBack())
intended(hasComponent(LoginActivity::class.java.name))
}
@Test
fun wrongLoginUrl() {
rule.launchActivity(Intent())
onView(withId(R.id.loginProgress))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)))
onView(withId(R.id.urlView)).perform(click()).perform(typeText("WRONGURL"))
onView(withId(R.id.signInButton)).perform(click())
onView(withId(R.id.urlView)).check(matches(isHintOrErrorEnabled()))
}
// TODO: Add tests for multiple false urls with dialog
@Test
fun emptyAuthData() {
rule.launchActivity(Intent())
onView(withId(R.id.urlView)).perform(click()).perform(typeText(url), closeSoftKeyboard())
onView(withId(R.id.withLogin)).perform(click())
onView(withId(R.id.signInButton)).perform(click())
onView(withId(R.id.loginView)).check(matches(isHintOrErrorEnabled()))
onView(withId(R.id.passwordView)).check(matches(isHintOrErrorEnabled()))
onView(withId(R.id.loginView)).perform(click()).perform(
typeText(username),
closeSoftKeyboard()
)
onView(withId(R.id.passwordView)).check(matches(isHintOrErrorEnabled()))
onView(withId(R.id.signInButton)).perform(click())
onView(withId(R.id.passwordView)).check(
matches(
isHintOrErrorEnabled()
)
)
}
@Test
fun wrongAuthData() {
rule.launchActivity(Intent())
onView(withId(R.id.urlView)).perform(click()).perform(typeText(url), closeSoftKeyboard())
onView(withId(R.id.withLogin)).perform(click())
onView(withId(R.id.loginView)).perform(click()).perform(
typeText(username),
closeSoftKeyboard()
)
onView(withId(R.id.passwordView)).perform(click()).perform(
typeText("WRONGPASS"),
closeSoftKeyboard()
)
onView(withId(R.id.signInButton)).perform(click())
onView(withId(R.id.urlView)).check(matches(isHintOrErrorEnabled()))
onView(withId(R.id.loginView)).check(matches(isHintOrErrorEnabled()))
onView(withId(R.id.passwordView)).check(matches(isHintOrErrorEnabled()))
}
@Test
fun workingAuth() {
rule.launchActivity(Intent())
onView(withId(R.id.urlView)).perform(click()).perform(typeText(url), closeSoftKeyboard())
onView(withId(R.id.withLogin)).perform(click())
onView(withId(R.id.loginView)).perform(click()).perform(
typeText(username),
closeSoftKeyboard()
)
onView(withId(R.id.passwordView)).perform(click()).perform(
typeText(password),
closeSoftKeyboard()
)
onView(withId(R.id.signInButton)).perform(click())
Thread.sleep(2000)
intended(hasComponent(HomeActivity::class.java.name))
}
@After
fun releaseIntents() {
Intents.release()
}
}

View File

@ -1,81 +0,0 @@
package bou.amine.apps.readerforselfossv2.android
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import androidx.test.InstrumentationRegistry.getInstrumentation
import androidx.test.espresso.intent.Intents
import androidx.test.espresso.intent.Intents.intended
import androidx.test.espresso.intent.Intents.times
import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
import androidx.test.rule.ActivityTestRule
import androidx.test.runner.AndroidJUnit4
import bou.amine.apps.readerforselfossv2.android.HomeActivity
import bou.amine.apps.readerforselfossv2.android.LoginActivity
import bou.amine.apps.readerforselfossv2.android.MainActivity
import bou.amine.apps.readerforselfossv2.android.utils.Config
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class MainActivityEspressoTest {
lateinit var intent: Intent
lateinit var preferencesEditor: SharedPreferences.Editor
private lateinit var url: String
private lateinit var username: String
private lateinit var password: String
@Rule @JvmField
val rule = ActivityTestRule(MainActivity::class.java, true, false)
@Before
fun setUp() {
intent = Intent()
val context = getInstrumentation().targetContext
// create a SharedPreferences editor
preferencesEditor = context.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE).edit()
url = BuildConfig.LOGIN_URL
username = BuildConfig.LOGIN_USERNAME
password = BuildConfig.LOGIN_PASSWORD
Intents.init()
}
@Test
fun checkFirstOpenLaunchesIntro() {
preferencesEditor.putString("url", "")
preferencesEditor.putString("password", "")
preferencesEditor.putString("login", "")
preferencesEditor.commit()
rule.launchActivity(intent)
intended(hasComponent(LoginActivity::class.java.name))
intended(hasComponent(HomeActivity::class.java.name), times(0))
}
@Test
fun checkNotFirstOpenLaunchesLogin() {
preferencesEditor.putString("url", url)
preferencesEditor.putString("password", password)
preferencesEditor.putString("login", username)
preferencesEditor.commit()
rule.launchActivity(intent)
intended(hasComponent(MainActivity::class.java.name))
intended(hasComponent(HomeActivity::class.java.name))
}
@After
fun releaseIntents() {
Intents.release()
}
}

View File

@ -1,29 +0,0 @@
package bou.amine.apps.readerforselfossv2.android
import androidx.test.espresso.matcher.ViewMatchers
import android.view.View
import android.widget.EditText
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.hamcrest.Matchers
import org.hamcrest.TypeSafeMatcher
fun isHintOrErrorEnabled(): Matcher<View> =
object : TypeSafeMatcher<View>() {
override fun describeTo(description: Description?) {
}
override fun matchesSafely(item: View?): Boolean {
if (item !is EditText) {
return false
}
return item.error.isNotEmpty()
}
}
fun withMenu(id: Int, titleId: Int): Matcher<View> =
Matchers.anyOf(
ViewMatchers.withId(id),
ViewMatchers.withText(titleId)
)

View File

@ -1,32 +0,0 @@
// TODO
//package bou.amine.apps.readerforselfossv2.android.utils
//
//import bou.amine.apps.readerforselfossv2.android.utils.Config
//import bou.amine.apps.readerforselfossv2.android.utils.parseDate
//import org.junit.Test
//
//class DateUtilsTest {
//
// @Test
// fun parseDateV4() {
//
// Config.apiVersion = 4
// val dateString = "2013-04-07T13:43:00+01:00"
//
// val milliseconds = parseDate(dateString).toEpochMilli()
// val correctMilliseconds : Long = 1365338580000
//
// assert(milliseconds == correctMilliseconds)
// }
//
// @Test
// fun parseDateV1() {
// Config.apiVersion = 0
// val dateString = "2013-04-07 13:43:00"
//
// val milliseconds = parseDate(dateString).toEpochMilli()
// val correctMilliseconds = 1365342180000
//
// assert(milliseconds == correctMilliseconds)
// }
//}

View File

@ -1,18 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="bou.amine.apps.readerforselfossv2.android">
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:allowBackup="false"
android:fullBackupContent="false"
tools:replace="android:allowBackup"
android:name=".MyApp"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:networkSecurityConfig="@xml/network_security_config"
android:theme="@style/NoBar">
android:theme="@style/NoBar"
android:dataExtractionRules="@xml/data_extraction_rules"
android:configChanges="uiMode">
<activity
android:name=".MainActivity"
android:theme="@style/SplashTheme"
@ -68,10 +72,6 @@
android:name=".ImageActivity">
</activity>
<meta-data
android:name="bou.amine.apps.readerforselfossv2.android.utils.glide.SelfSignedGlideModule"
android:value="GlideModule" />
<meta-data android:name="android.webkit.WebView.MetricsOptOut"
android:value="true" />

View File

@ -7,12 +7,10 @@ import android.widget.*
import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityAddSourceBinding
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
import bou.amine.apps.readerforselfossv2.android.themes.Toppings
import bou.amine.apps.readerforselfossv2.android.utils.Config
import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlValid
import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlInvalid
import bou.amine.apps.readerforselfossv2.model.NetworkUnavailableException
import bou.amine.apps.readerforselfossv2.repository.Repository
import com.ftinc.scoop.Scoop
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -25,50 +23,25 @@ class AddSourceActivity : AppCompatActivity(), DIAware {
private var mSpoutsValue: String? = null
private lateinit var appColors: AppColors
private lateinit var binding: ActivityAddSourceBinding
override val di by closestDI()
private val repository : Repository by instance()
private val appSettingsService : AppSettingsService by instance()
override fun onCreate(savedInstanceState: Bundle?) {
appColors = AppColors(this@AddSourceActivity)
super.onCreate(savedInstanceState)
binding = ActivityAddSourceBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
val scoop = Scoop.getInstance()
scoop.bind(this, Toppings.PRIMARY.value, binding.toolbar)
scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.value)
val drawable = binding.nameInput.background
drawable.setTint(appColors.colorAccent)
// TODO: clean
binding.nameInput.background = drawable
val drawable1 = binding.sourceUri.background
drawable1.setTint(appColors.colorAccent)
binding.sourceUri.background = drawable1
val drawable2 = binding.tags.background
drawable2.setTint(appColors.colorAccent)
binding.tags.background = drawable2
setSupportActionBar(binding.toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
maybeGetDetailsFromIntentSharing(intent, binding.sourceUri, binding.nameInput)
binding.saveBtn.setTextColor(appColors.colorAccent)
binding.saveBtn.setOnClickListener {
handleSaveSource(
binding.tags,
@ -80,9 +53,9 @@ class AddSourceActivity : AppCompatActivity(), DIAware {
override fun onResume() {
super.onResume()
val config = Config()
if (config.baseUrl.isEmpty() || !config.baseUrl.isBaseUrlValid(this@AddSourceActivity)) {
val baseUrl = appSettingsService.getBaseUrl()
if (baseUrl.isEmpty() || baseUrl.isBaseUrlInvalid()) {
mustLoginToAddSource()
} else {
handleSpoutsSpinner(binding.spoutsSpinner, binding.progress, binding.formContainer)
@ -109,33 +82,40 @@ class AddSourceActivity : AppCompatActivity(), DIAware {
}
fun handleSpoutFailure(networkIssue: Boolean = false) {
Toast.makeText(
this@AddSourceActivity,
if (networkIssue) R.string.cant_get_spouts_no_network else R.string.cant_get_spouts,
Toast.LENGTH_SHORT
).show()
mProgress.visibility = View.GONE
}
CoroutineScope(Dispatchers.Main).launch {
val items = repository.getSpouts()
if (items != null) {
try {
val items = repository.getSpouts()
if (items.isNotEmpty()) {
val itemsStrings = items.map { it.value.name }
for ((key, value) in items) {
spoutsKV[value.name] = key
}
val itemsStrings = items.map { it.value.name }
for ((key, value) in items) {
spoutsKV[value.name] = key
mProgress.visibility = View.GONE
formContainer.visibility = View.VISIBLE
val spinnerArrayAdapter =
ArrayAdapter(
this@AddSourceActivity,
android.R.layout.simple_spinner_item,
itemsStrings
)
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spoutsSpinner.adapter = spinnerArrayAdapter
} else {
handleSpoutFailure()
}
mProgress.visibility = View.GONE
formContainer.visibility = View.VISIBLE
val spinnerArrayAdapter =
ArrayAdapter(
this@AddSourceActivity,
android.R.layout.simple_spinner_item,
itemsStrings
)
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spoutsSpinner.adapter = spinnerArrayAdapter
} else {
Toast.makeText(
this@AddSourceActivity,
R.string.cant_get_spouts,
Toast.LENGTH_SHORT
).show()
mProgress.visibility = View.GONE
} catch (e: NetworkUnavailableException) {
handleSpoutFailure(networkIssue = 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
@ -12,13 +13,12 @@ import android.view.inputmethod.EditorInfo
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityLoginBinding
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlValid
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable
import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlInvalid
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import com.mikepenz.aboutlibraries.LibsBuilder
import com.russhwolf.settings.Settings
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -26,24 +26,22 @@ import org.kodein.di.DIAware
import org.kodein.di.android.closestDI
import org.kodein.di.instance
class LoginActivity() : AppCompatActivity(), DIAware {
class LoginActivity : AppCompatActivity(), DIAware {
private var inValidCount: Int = 0
private var isWithSelfSignedCert = false
private var isWithLogin = false
private var isWithHTTPLogin = false
private val settings = Settings()
private lateinit var appColors: AppColors
private lateinit var binding: ActivityLoginBinding
override val di by closestDI()
private val repository : Repository by instance()
private val appSettingsService : AppSettingsService by instance()
override fun onCreate(savedInstanceState: Bundle?) {
appColors = AppColors(this@LoginActivity)
super.onCreate(savedInstanceState)
handleTheme()
binding = ActivityLoginBinding.inflate(layoutInflater)
val view = binding.root
@ -53,22 +51,20 @@ class LoginActivity() : AppCompatActivity(), DIAware {
handleBaseUrlFail()
if (settings.getString("url", "").isNotEmpty()) {
if (appSettingsService.getBaseUrl().isNotEmpty()) {
goToMain()
}
handleActions()
}
@SuppressLint("WrongConstant") // Constant is fetched from the settings
private fun handleTheme() {
AppCompatDelegate.setDefaultNightMode(appSettingsService.getCurrentTheme())
}
private fun handleActions() {
binding.withSelfhostedCert.setOnCheckedChangeListener { _, b ->
isWithSelfSignedCert = !isWithSelfSignedCert
val visi: Int = if (b) View.VISIBLE else View.GONE
binding.warningText.visibility = visi
}
binding.passwordView.setOnEditorActionListener(
TextView.OnEditorActionListener { _, id, _ ->
if (id == R.id.loginView || id == EditorInfo.IME_NULL) {
@ -88,14 +84,6 @@ class LoginActivity() : AppCompatActivity(), DIAware {
binding.loginView.visibility = visi
binding.passwordView.visibility = visi
}
binding.withHttpLogin.setOnCheckedChangeListener { _, b ->
isWithHTTPLogin = !isWithHTTPLogin
val visi: Int = if (b) View.VISIBLE else View.GONE
binding.httpLoginView.visibility = visi
binding.httpPasswordView.visibility = visi
}
}
private fun handleBaseUrlFail() {
@ -112,22 +100,20 @@ class LoginActivity() : AppCompatActivity(), DIAware {
}
private fun goToMain() {
CoroutineScope(Dispatchers.Main).launch {
repository.updateApiVersion()
}
val intent = Intent(this, HomeActivity::class.java)
startActivity(intent)
finish()
}
private fun preferenceError(t: Throwable) {
settings.remove("url")
settings.remove("login")
settings.remove("httpUserName")
settings.remove("password")
settings.remove("httpPassword")
private fun preferenceError() {
appSettingsService.resetLoginInformation()
binding.urlView.error = getString(R.string.wrong_infos)
binding.loginView.error = getString(R.string.wrong_infos)
binding.passwordView.error = getString(R.string.wrong_infos)
binding.httpLoginView.error = getString(R.string.wrong_infos)
binding.httpPasswordView.error = getString(R.string.wrong_infos)
}
private fun attemptLogin() {
@ -135,21 +121,17 @@ class LoginActivity() : AppCompatActivity(), DIAware {
// Reset errors.
binding.urlView.error = null
binding.loginView.error = null
binding.httpLoginView.error = null
binding.passwordView.error = null
binding.httpPasswordView.error = null
// Store values at the time of the login attempt.
val url = binding.urlView.text.toString()
val login = binding.loginView.text.toString()
val httpLogin = binding.httpLoginView.text.toString()
val password = binding.passwordView.text.toString()
val httpPassword = binding.httpPasswordView.text.toString()
val url = binding.urlView.text.toString().trim()
val login = binding.loginView.text.toString().trim()
val password = binding.passwordView.text.toString().trim()
var cancel = false
var focusView: View? = null
if (!url.isBaseUrlValid(this@LoginActivity)) {
if (url.isBaseUrlInvalid()) {
binding.urlView.error = getString(R.string.login_url_problem)
focusView = binding.urlView
cancel = true
@ -181,36 +163,20 @@ class LoginActivity() : AppCompatActivity(), DIAware {
}
}
if (isWithHTTPLogin) {
if (TextUtils.isEmpty(httpPassword)) {
binding.httpPasswordView.error = getString(R.string.error_invalid_password)
focusView = binding.httpPasswordView
cancel = true
}
if (TextUtils.isEmpty(httpLogin)) {
binding.httpLoginView.error = getString(R.string.error_field_required)
focusView = binding.httpLoginView
cancel = true
}
}
if (cancel) {
focusView?.requestFocus()
} else {
showProgress(true)
repository.refreshLoginInformation(url, login, password, httpLogin, httpPassword, isWithSelfSignedCert)
repository.refreshLoginInformation(url, login, password)
if (this@LoginActivity.isNetworkAvailable(this@LoginActivity.findViewById(R.id.loginForm))) {
CoroutineScope(Dispatchers.IO).launch {
val result = repository.login()
if (result) {
goToMain()
} else {
CoroutineScope(Dispatchers.Main).launch {
preferenceError(Exception("Not success"))
}
CoroutineScope(Dispatchers.IO).launch {
val result = repository.login()
if (result) {
goToMain()
} else {
CoroutineScope(Dispatchers.Main).launch {
preferenceError()
}
}
}

View File

@ -3,46 +3,86 @@ package bou.amine.apps.readerforselfossv2.android
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.content.res.Configuration
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Build
import android.widget.ImageView
import android.widget.Toast
import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.app.AppCompatDelegate.*
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.utils.Config
import bou.amine.apps.readerforselfossv2.android.utils.glide.loadMaybeBasicAuth
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAccessible
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.ftinc.scoop.Scoop
import com.github.ln_12.library.ConnectivityStatus
import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader
import com.mikepenz.materialdrawer.util.DrawerImageLoader
import com.russhwolf.settings.Settings
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.kodein.di.*
class MyApp : MultiDexApplication(), DIAware {
override val di by DI.lazy {
import(networkModule)
bind<Repository>() with singleton { Repository(instance(), instance()) }
bind<DriverFactory>() with singleton { DriverFactory(applicationContext) }
bind<ReaderForSelfossDB>() with singleton { ReaderForSelfossDB(driverFactory.createDriver()) }
bind<Repository>() with singleton { Repository(instance(), instance(), isConnectionAvailable, instance()) }
bind<ConnectivityStatus>() with singleton { ConnectivityStatus(applicationContext) }
bind<AppViewModel>() with singleton { AppViewModel(repository = instance()) }
}
private lateinit var config: Config
private lateinit var settings : Settings
private val repository: Repository by instance()
private val viewModel: AppViewModel by instance()
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()
config = Config()
settings = Settings()
Napier.base(DebugAntilog())
initDrawerImageLoader()
initTheme()
tryToHandleBug()
handleNotificationChannels()
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
}
Toast.makeText(
applicationContext,
toastMessage,
Toast.LENGTH_SHORT
).show()
}
}
}
private fun handleNotificationChannels() {
@ -51,11 +91,11 @@ class MyApp : MultiDexApplication(), DIAware {
val name = getString(R.string.notification_channel_sync)
val importance = NotificationManager.IMPORTANCE_LOW
val mChannel = NotificationChannel(Config.syncChannelId, name, importance)
val mChannel = NotificationChannel(AppSettingsService.syncChannelId, name, importance)
val newItemsChannelname = getString(R.string.new_items_channel_sync)
val newItemsChannelimportance = NotificationManager.IMPORTANCE_DEFAULT
val newItemsChannelmChannel = NotificationChannel(Config.newItemsChannelId, newItemsChannelname, newItemsChannelimportance)
val newItemsChannelmChannel = NotificationChannel(AppSettingsService.newItemsChannelId, newItemsChannelname, newItemsChannelimportance)
notificationManager.createNotificationChannel(mChannel)
notificationManager.createNotificationChannel(newItemsChannelmChannel)
@ -66,7 +106,7 @@ class MyApp : MultiDexApplication(), DIAware {
DrawerImageLoader.init(object : AbstractDrawerImageLoader() {
override fun set(imageView: ImageView, uri: Uri, placeholder: Drawable, tag: String?) {
Glide.with(imageView.context)
.loadMaybeBasicAuth(config, uri.toString())
.load(uri.toString())
.apply(RequestOptions.fitCenterTransform().placeholder(placeholder))
.into(imageView)
}
@ -81,25 +121,31 @@ class MyApp : MultiDexApplication(), DIAware {
})
}
private fun initTheme() {
Scoop.waffleCone()
.addFlavor(getString(R.string.default_theme), R.style.NoBar, true)
.addFlavor(getString(R.string.default_dark_theme), R.style.NoBarDark, false)
.setSharedPreferences(PreferenceManager.getDefaultSharedPreferences(this))
.initialize()
}
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
} else {
oldHandler.uncaughtException(thread, e)
}
}
}
class AppLifeCycleObserver(val connectivityStatus: ConnectivityStatus, val repository: Repository) : DefaultLifecycleObserver {
override fun onResume(owner: LifecycleOwner) {
super.onResume(owner)
repository.connectionMonitored = true
connectivityStatus.start()
}
override fun onPause(owner: LifecycleOwner) {
repository.connectionMonitored = false
connectivityStatus.stop()
super.onPause(owner)
}
}
}

View File

@ -8,22 +8,13 @@ import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.room.Room
import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityReaderBinding
import bou.amine.apps.readerforselfossv2.android.fragments.ArticleFragment
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_1_2
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_2_3
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_3_4
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
import bou.amine.apps.readerforselfossv2.android.themes.Toppings
import bou.amine.apps.readerforselfossv2.android.utils.toggleStar
import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
import com.ftinc.scoop.Scoop
import com.russhwolf.settings.Settings
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -33,27 +24,21 @@ import org.kodein.di.instance
class ReaderActivity : AppCompatActivity(), DIAware {
private var markOnScroll: Boolean = false
private var currentItem: Int = 0
private lateinit var appColors: AppColors
private lateinit var toolbarMenu: Menu
private lateinit var db: AppDatabase
private lateinit var binding: ActivityReaderBinding
private var activeAlignment: Int = 1
private val JUSTIFY = 1
private val ALIGN_LEFT = 2
override val di by closestDI()
private val repository : Repository by instance()
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)
}
}
@ -65,32 +50,17 @@ class ReaderActivity : AppCompatActivity(), DIAware {
showMenuItem(false)
}
private var settings = Settings()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
appColors = AppColors(this)
binding = ActivityReaderBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
db = Room.databaseBuilder(
applicationContext,
AppDatabase::class.java, "selfoss-database"
).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).addMigrations(MIGRATION_3_4).build()
val scoop = Scoop.getInstance()
scoop.bind(this, Toppings.PRIMARY.value, binding.toolBar)
scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.value)
setSupportActionBar(binding.toolBar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
markOnScroll = settings.getBoolean("mark_on_scroll", false)
activeAlignment = settings.getInt("text_align", JUSTIFY)
if (allItems.isEmpty()) {
finish()
}
@ -110,12 +80,11 @@ class ReaderActivity : AppCompatActivity(), DIAware {
}
private fun readItem(item: SelfossModel.Item) {
if (markOnScroll) {
CoroutineScope(Dispatchers.IO).launch {
repository.markAsRead(item.id)
// TODO: Handle failure
}
if (appSettingsService.isMarkOnScrollEnabled()) {
CoroutineScope(Dispatchers.IO).launch {
repository.markAsRead(item)
}
}
}
override fun onSaveInstanceState(oldInstanceState: Bundle) {
@ -128,19 +97,22 @@ class ReaderActivity : AppCompatActivity(), DIAware {
override fun getItemCount(): Int = allItems.size
override fun createFragment(position: Int): Fragment = ArticleFragment.newInstance(allItems[position])
override fun createFragment(position: Int): Fragment =
ArticleFragment.newInstance(allItems[position])
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
return when (keyCode) {
KeyEvent.KEYCODE_VOLUME_DOWN -> {
val currentFragment = supportFragmentManager.findFragmentByTag("f" + binding.pager.currentItem) as ArticleFragment
val currentFragment =
supportFragmentManager.findFragmentByTag("f" + binding.pager.currentItem) as ArticleFragment
currentFragment.scrollDown()
true
}
KeyEvent.KEYCODE_VOLUME_UP -> {
val currentFragment = supportFragmentManager.findFragmentByTag("f" + binding.pager.currentItem) as ArticleFragment
val currentFragment =
supportFragmentManager.findFragmentByTag("f" + binding.pager.currentItem) as ArticleFragment
currentFragment.scrollUp()
true
}
@ -151,7 +123,7 @@ class ReaderActivity : AppCompatActivity(), DIAware {
}
private fun alignmentMenu() {
val showJustify = activeAlignment == ALIGN_LEFT
val showJustify = appSettingsService.getActiveAllignment() == AppSettingsService.ALIGN_LEFT
toolbarMenu.findItem(R.id.align_left).isVisible = !showJustify
toolbarMenu.findItem(R.id.align_justify).isVisible = showJustify
}
@ -169,19 +141,19 @@ class ReaderActivity : AppCompatActivity(), DIAware {
alignmentMenu()
binding.pager.registerOnPageChangeCallback(
object : ViewPager2.OnPageChangeCallback() {
object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
if (allItems[position].starred) {
canRemoveFromFavorite()
} else {
canFavorite()
}
readItem(allItems[position])
if (allItems[position].starred) {
canRemoveFromFavorite()
} else {
canFavorite()
}
readItem(allItems[position])
}
}
)
return true
@ -190,7 +162,7 @@ class ReaderActivity : AppCompatActivity(), DIAware {
override fun onOptionsItemSelected(item: MenuItem): Boolean {
fun afterSave() {
allItems[binding.pager.currentItem] =
allItems[binding.pager.currentItem].toggleStar()
allItems[binding.pager.currentItem].toggleStar()
canRemoveFromFavorite()
}
@ -207,34 +179,32 @@ class ReaderActivity : AppCompatActivity(), DIAware {
R.id.star -> {
if (allItems[binding.pager.currentItem].starred) {
CoroutineScope(Dispatchers.IO).launch {
repository.unstarr(allItems[binding.pager.currentItem].id)
repository.unstarr(allItems[binding.pager.currentItem])
// TODO: Handle failure
}
afterUnsave()
} else {
CoroutineScope(Dispatchers.IO).launch {
repository.starr(allItems[binding.pager.currentItem].id)
repository.starr(allItems[binding.pager.currentItem])
// TODO: Handle failure
}
afterSave()
}
}
R.id.align_left -> {
activeAlignment = ALIGN_LEFT
switchAlignmentSetting()
switchAlignmentSetting(AppSettingsService.ALIGN_LEFT)
refreshFragment()
}
R.id.align_justify -> {
activeAlignment = JUSTIFY
switchAlignmentSetting()
switchAlignmentSetting(AppSettingsService.JUSTIFY)
refreshFragment()
}
}
return super.onOptionsItemSelected(item)
}
private fun switchAlignmentSetting() {
settings.putInt("text_align", activeAlignment)
private fun switchAlignmentSetting(allignment: Int) {
appSettingsService.changeAllignment(allignment)
alignmentMenu()
}

View File

@ -8,12 +8,8 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import bou.amine.apps.readerforselfossv2.android.adapters.SourcesListAdapter
import bou.amine.apps.readerforselfossv2.android.databinding.ActivitySourcesBinding
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
import bou.amine.apps.readerforselfossv2.android.themes.Toppings
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable
import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
import com.ftinc.scoop.Scoop
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -23,21 +19,15 @@ import org.kodein.di.instance
class SourcesActivity : AppCompatActivity(), DIAware {
private lateinit var appColors: AppColors
private lateinit var binding: ActivitySourcesBinding
override val di by closestDI()
private val repository : Repository by instance()
override fun onCreate(savedInstanceState: Bundle?) {
appColors = AppColors(this@SourcesActivity)
binding = ActivitySourcesBinding.inflate(layoutInflater)
val view = binding.root
val scoop = Scoop.getInstance()
scoop.bind(this, Toppings.PRIMARY.value, binding.toolbar)
scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.value)
super.onCreate(savedInstanceState)
setContentView(view)
@ -46,8 +36,8 @@ class SourcesActivity : AppCompatActivity(), DIAware {
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
binding.fab.rippleColor = appColors.colorAccentDark
binding.fab.backgroundTintList = ColorStateList.valueOf(appColors.colorAccent)
binding.fab.rippleColor = resources.getColor(R.color.colorAccentDark)
binding.fab.backgroundTintList = ColorStateList.valueOf(resources.getColor(R.color.colorAccent))
}
override fun onStop() {
@ -64,30 +54,21 @@ class SourcesActivity : AppCompatActivity(), DIAware {
binding.recyclerView.setHasFixedSize(true)
binding.recyclerView.layoutManager = mLayoutManager
if (this@SourcesActivity.isNetworkAvailable(binding.recyclerView)) {
CoroutineScope(Dispatchers.Main).launch {
val response = repository.getSources()
if (response != null) {
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,
R.string.cant_get_sources,
Toast.LENGTH_SHORT
).show()
}
CoroutineScope(Dispatchers.Main).launch {
val response = repository.getSources()
if (response.isNotEmpty()) {
items = response
val mAdapter = SourcesListAdapter(
this@SourcesActivity, items
)
binding.recyclerView.adapter = mAdapter
mAdapter.notifyDataSetChanged()
} else {
Toast.makeText(
this@SourcesActivity,
R.string.cant_get_sources,
Toast.LENGTH_SHORT
).show()
}
}

View File

@ -9,16 +9,19 @@ import android.widget.ImageView.ScaleType
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.*
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
import bou.amine.apps.readerforselfossv2.android.utils.*
import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper
import bou.amine.apps.readerforselfossv2.android.model.toTextDrawableString
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.network.isNetworkAvailable
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.rest.SelfossModel
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
import bou.amine.apps.readerforselfossv2.utils.getIcon
import bou.amine.apps.readerforselfossv2.utils.getThumbnail
import com.amulyakhare.textdrawable.TextDrawable
import com.amulyakhare.textdrawable.util.ColorGenerator
import com.bumptech.glide.Glide
@ -32,13 +35,6 @@ import org.kodein.di.instance
class ItemCardAdapter(
override val app: Activity,
override var items: ArrayList<SelfossModel.Item>,
override val db: AppDatabase,
private val helper: CustomTabActivityHelper,
private val internalBrowser: Boolean,
private val articleViewer: Boolean,
private val fullHeightCards: Boolean,
override val appColors: AppColors,
override val config: Config,
override val updateItems: (ArrayList<SelfossModel.Item>) -> Unit
) : ItemsAdapter<ItemCardAdapter.ViewHolder>() {
private val c: Context = app.baseContext
@ -47,7 +43,8 @@ class ItemCardAdapter(
c.resources.getDimension(R.dimen.card_image_max_height).toInt()
override val di: DI by closestDI(app)
override val repository : Repository by instance()
override val repository: Repository by instance()
override val appSettingsService: AppSettingsService by instance()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = CardItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
@ -59,15 +56,15 @@ class ItemCardAdapter(
val itm = items[position]
binding.favButton.isSelected = itm.starred
binding.title.text = itm.getTitleDecoded()
binding.title.text = itm.title.getHtmlDecoded()
binding.title.setOnTouchListener(LinkOnTouchListener())
binding.title.setLinkTextColor(appColors.colorAccent)
binding.title.setLinkTextColor(c.resources.getColor(R.color.colorAccent))
binding.sourceTitleAndDate.text = itm.sourceAndDateText(repository.dateUtils)
binding.sourceTitleAndDate.text = itm.sourceAndDateText()
if (!fullHeightCards) {
if (!appSettingsService.isFullHeightCardsEnabled()) {
binding.itemImage.maxHeight = imageMaxHeight
binding.itemImage.scaleType = ScaleType.CENTER_CROP
}
@ -78,20 +75,20 @@ class ItemCardAdapter(
binding.itemImage.setImageDrawable(null)
} else {
binding.itemImage.visibility = View.VISIBLE
c.bitmapCenterCrop(config, itm.getThumbnail(repository.baseUrl), binding.itemImage)
c.bitmapCenterCrop(itm.getThumbnail(repository.baseUrl), binding.itemImage)
}
if (itm.getIcon(repository.baseUrl).isEmpty()) {
val color = generator.getColor(itm.getSourceTitle())
val color = generator.getColor(itm.title.getHtmlDecoded())
val drawable =
TextDrawable
.builder()
.round()
.build(itm.getSourceTitle().toTextDrawableString(c), color)
TextDrawable
.builder()
.round()
.build(itm.title.getHtmlDecoded().toTextDrawableString(), color)
binding.sourceImage.setImageDrawable(drawable)
} else {
c.circularBitmapDrawable(config, itm.getIcon(repository.baseUrl), binding.sourceImage)
c.circularBitmapDrawable(itm.getIcon(repository.baseUrl), binding.sourceImage)
}
}
}
@ -103,54 +100,43 @@ class ItemCardAdapter(
inner class ViewHolder(val binding: CardItemBinding) : RecyclerView.ViewHolder(binding.root) {
init {
handleClickListeners()
handleCustomTabActions()
handleLinkOpening()
}
private fun handleClickListeners() {
binding.favButton.setOnClickListener {
val item = items[bindingAdapterPosition]
if (c.isNetworkAvailable()) {
if (item.starred) {
CoroutineScope(Dispatchers.IO).launch {
repository.unstarr(item.id)
// TODO: Handle failure
}
item.starred = false
binding.favButton.isSelected = false
} else {
CoroutineScope(Dispatchers.IO).launch {
repository.starr(item.id)
// TODO: Handle failure
}
item.starred = true
binding.favButton.isSelected = true
if (item.starred) {
CoroutineScope(Dispatchers.IO).launch {
repository.unstarr(item)
}
binding.favButton.isSelected = false
} else {
CoroutineScope(Dispatchers.IO).launch {
repository.starr(item)
}
binding.favButton.isSelected = true
}
}
binding.shareBtn.setOnClickListener {
val item = items[bindingAdapterPosition]
c.shareLink(item.getLinkDecoded(), item.getTitleDecoded())
}
binding.browserBtn.setOnClickListener {
c.openInBrowserAsNewTask(items[bindingAdapterPosition])
}
binding.shareBtn.setOnClickListener {
val item = items[bindingAdapterPosition]
c.shareLink(item.getLinkDecoded(), item.title.getHtmlDecoded())
}
private fun handleCustomTabActions() {
val customTabsIntent = c.buildCustomTabsIntent()
helper.bindCustomTabsService(app)
binding.browserBtn.setOnClickListener {
c.openInBrowserAsNewTask(items[bindingAdapterPosition])
}
}
private fun handleLinkOpening() {
binding.root.setOnClickListener {
c.openItemUrl(
items,
bindingAdapterPosition,
items[bindingAdapterPosition].getLinkDecoded(),
customTabsIntent,
internalBrowser,
articleViewer,
appSettingsService.isArticleViewerEnabled(),
app
)
}

View File

@ -5,16 +5,19 @@ import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import bou.amine.apps.readerforselfossv2.android.R
import bou.amine.apps.readerforselfossv2.android.databinding.ListItemBinding
import bou.amine.apps.readerforselfossv2.android.model.*
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
import bou.amine.apps.readerforselfossv2.android.utils.*
import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper
import bou.amine.apps.readerforselfossv2.android.model.toTextDrawableString
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.openItemUrl
import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
import bou.amine.apps.readerforselfossv2.utils.getIcon
import bou.amine.apps.readerforselfossv2.utils.getThumbnail
import com.amulyakhare.textdrawable.TextDrawable
import com.amulyakhare.textdrawable.util.ColorGenerator
import org.kodein.di.DI
@ -24,12 +27,6 @@ import org.kodein.di.instance
class ItemListAdapter(
override val app: Activity,
override var items: ArrayList<SelfossModel.Item>,
override val db: AppDatabase,
private val helper: CustomTabActivityHelper,
private val internalBrowser: Boolean,
private val articleViewer: Boolean,
override val appColors: AppColors,
override val config: Config,
override val updateItems: (ArrayList<SelfossModel.Item>) -> Unit
) : ItemsAdapter<ItemListAdapter.ViewHolder>() {
private val generator: ColorGenerator = ColorGenerator.MATERIAL
@ -37,6 +34,7 @@ class ItemListAdapter(
override val di: DI by closestDI(app)
override val repository : Repository by instance()
override val appSettingsService : AppSettingsService by instance()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = ListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
@ -47,31 +45,31 @@ class ItemListAdapter(
with(holder) {
val itm = items[position]
binding.title.text = itm.getTitleDecoded()
binding.title.text = itm.title.getHtmlDecoded()
binding.title.setOnTouchListener(LinkOnTouchListener())
binding.title.setLinkTextColor(appColors.colorAccent)
binding.title.setLinkTextColor(c.resources.getColor(R.color.colorAccent))
binding.sourceTitleAndDate.text = itm.sourceAndDateText(repository.dateUtils)
binding.sourceTitleAndDate.text = itm.sourceAndDateText()
if (itm.getThumbnail(repository.baseUrl).isEmpty()) {
if (itm.getIcon(repository.baseUrl).isEmpty()) {
val color = generator.getColor(itm.getSourceTitle())
val color = generator.getColor(itm.title.getHtmlDecoded())
val drawable =
TextDrawable
.builder()
.round()
.build(itm.getSourceTitle().toTextDrawableString(c), color)
.build(itm.title.getHtmlDecoded().toTextDrawableString(), color)
binding.itemImage.setImageDrawable(drawable)
} else {
c.circularBitmapDrawable(config, itm.getIcon(repository.baseUrl), binding.itemImage)
c.circularBitmapDrawable(itm.getIcon(repository.baseUrl), binding.itemImage)
}
} else {
c.bitmapCenterCrop(config, itm.getThumbnail(repository.baseUrl), binding.itemImage)
c.bitmapCenterCrop(itm.getThumbnail(repository.baseUrl), binding.itemImage)
}
}
}
@ -81,21 +79,16 @@ class ItemListAdapter(
inner class ViewHolder(val binding: ListItemBinding) : RecyclerView.ViewHolder(binding.root) {
init {
handleCustomTabActions()
handleLinkOpening()
}
private fun handleCustomTabActions() {
val customTabsIntent = c.buildCustomTabsIntent()
helper.bindCustomTabsService(app)
private fun handleLinkOpening() {
binding.root.setOnClickListener {
c.openItemUrl(
items,
bindingAdapterPosition,
items[bindingAdapterPosition].getLinkDecoded(),
customTabsIntent,
internalBrowser,
articleViewer,
appSettingsService.isArticleViewerEnabled(),
app
)
}

View File

@ -5,11 +5,9 @@ import android.graphics.Color
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import bou.amine.apps.readerforselfossv2.android.R
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
import bou.amine.apps.readerforselfossv2.android.utils.Config
import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import bou.amine.apps.readerforselfossv2.utils.ItemType
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.CoroutineScope
@ -20,10 +18,8 @@ import org.kodein.di.DIAware
abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapter<VH>(), DIAware {
abstract var items: ArrayList<SelfossModel.Item>
abstract val repository: Repository
abstract val db: AppDatabase
abstract val appSettingsService: AppSettingsService
abstract val app: Activity
abstract val appColors: AppColors
abstract val config: Config
abstract val updateItems: (ArrayList<SelfossModel.Item>) -> Unit
fun updateAllItems(items: ArrayList<SelfossModel.Item>) {
@ -32,7 +28,7 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
updateItems(this.items)
}
private fun unmarkSnackbar(i: SelfossModel.Item, position: Int) {
private fun unmarkSnackbar(item: SelfossModel.Item, position: Int) {
val s = Snackbar
.make(
app.findViewById(R.id.coordLayout),
@ -41,7 +37,7 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
)
.setAction(R.string.undo_string) {
CoroutineScope(Dispatchers.IO).launch {
unreadItemAtIndex(position, false)
unreadItemAtIndex(item, position, false)
}
}
@ -51,7 +47,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),
@ -59,7 +55,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
@ -70,39 +66,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.id)
repository.markAsRead(item)
}
if (repository.displayedItems == ItemType.UNREAD) {
items.remove(i)
items.remove(item)
notifyItemRemoved(position)
updateItems(items)
} else {
notifyItemChanged(position)
}
if (showSnackbar) {
unmarkSnackbar(i, 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].id)
// Todo: SharedItems.unreadItem(app, api, db, items[position])
// TODO: update db
repository.unmarkAsRead(item)
}
notifyItemChanged(position)
if (showSnackbar) {
markSnackbar(position)
markSnackbar(item, position)
}
}

View File

@ -10,14 +10,12 @@ import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.RecyclerView
import bou.amine.apps.readerforselfossv2.android.R
import bou.amine.apps.readerforselfossv2.android.databinding.SourceListItemBinding
import bou.amine.apps.readerforselfossv2.android.model.getIcon
import bou.amine.apps.readerforselfossv2.android.model.getTitleDecoded
import bou.amine.apps.readerforselfossv2.android.utils.Config
import bou.amine.apps.readerforselfossv2.android.model.toTextDrawableString
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularBitmapDrawable
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable
import bou.amine.apps.readerforselfossv2.android.utils.toTextDrawableString
import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
import bou.amine.apps.readerforselfossv2.utils.getIcon
import com.amulyakhare.textdrawable.TextDrawable
import com.amulyakhare.textdrawable.util.ColorGenerator
import kotlinx.coroutines.CoroutineScope
@ -34,7 +32,6 @@ class SourcesListAdapter(
) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>(), DIAware {
private val c: Context = app.baseContext
private val generator: ColorGenerator = ColorGenerator.MATERIAL
private lateinit var config: Config
private lateinit var binding: SourceListItemBinding
override val di: DI by closestDI(app)
@ -47,27 +44,30 @@ class SourcesListAdapter(
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val itm = items[position]
config = Config()
if (itm.getIcon(repository.baseUrl).isEmpty()) {
val color = generator.getColor(itm.getTitleDecoded())
val color = generator.getColor(itm.title.getHtmlDecoded())
val drawable =
TextDrawable
.builder()
.round()
.build(itm.getTitleDecoded().toTextDrawableString(c), color)
.build(itm.title.getHtmlDecoded().toTextDrawableString(), color)
binding.itemImage.setImageDrawable(drawable)
} else {
c.circularBitmapDrawable(config, itm.getIcon(repository.baseUrl), binding.itemImage)
c.circularBitmapDrawable(itm.getIcon(repository.baseUrl), binding.itemImage)
}
binding.sourceTitle.text = itm.getTitleDecoded()
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()
@ -78,21 +78,19 @@ class SourcesListAdapter(
val deleteBtn: Button = mView.findViewById(R.id.deleteBtn)
deleteBtn.setOnClickListener {
if (c.isNetworkAvailable(null)) {
val (id) = items[adapterPosition]
CoroutineScope(Dispatchers.IO).launch {
val successfullyDeletedSource = repository.deleteSource(id)
if (successfullyDeletedSource) {
items.removeAt(adapterPosition)
notifyItemRemoved(adapterPosition)
notifyItemRangeChanged(adapterPosition, itemCount)
} else {
Toast.makeText(
app,
R.string.can_delete_source,
Toast.LENGTH_SHORT
).show()
}
val (id) = items[bindingAdapterPosition]
CoroutineScope(Dispatchers.IO).launch {
val successfullyDeletedSource = repository.deleteSource(id)
if (successfullyDeletedSource) {
items.removeAt(bindingAdapterPosition)
notifyItemRemoved(bindingAdapterPosition)
notifyItemRangeChanged(bindingAdapterPosition, itemCount)
} else {
Toast.makeText(
app,
R.string.can_delete_source,
Toast.LENGTH_SHORT
).show()
}
}
}

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-bou.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

@ -8,24 +8,16 @@ import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationCompat.PRIORITY_DEFAULT
import androidx.core.app.NotificationCompat.PRIORITY_LOW
import androidx.room.Room
import androidx.work.Worker
import androidx.work.WorkerParameters
import bou.amine.apps.readerforselfossv2.android.MainActivity
import bou.amine.apps.readerforselfossv2.android.MyApp
import bou.amine.apps.readerforselfossv2.android.R
import bou.amine.apps.readerforselfossv2.android.model.preloadImages
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
import bou.amine.apps.readerforselfossv2.android.persistence.entities.ActionEntity
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_1_2
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_2_3
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_3_4
import bou.amine.apps.readerforselfossv2.android.utils.Config
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAccessible
import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
import bou.amine.apps.readerforselfossv2.utils.ItemType
import com.russhwolf.settings.Settings
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -33,79 +25,40 @@ import org.kodein.di.DIAware
import org.kodein.di.instance
import java.util.*
import kotlin.concurrent.schedule
import kotlin.concurrent.thread
class LoadingWorker(val context: Context, params: WorkerParameters) : Worker(context, params), DIAware {
lateinit var db: AppDatabase
override val di by lazy { (applicationContext as MyApp).di }
private val repository : Repository by instance()
private val appSettingsService : AppSettingsService by instance()
override fun doWork(): Result {
val settings = Settings()
val periodicRefresh = settings.getBoolean("periodic_refresh", false)
if (periodicRefresh) {
if (appSettingsService.isPeriodicRefreshEnabled() && isNetworkAccessible(context)) {
if (context.isNetworkAvailable()) {
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, Config.syncChannelId)
.setContentTitle(context.getString(R.string.loading_notification_title))
.setContentText(context.getString(R.string.loading_notification_text))
.setOngoing(true)
.setPriority(PRIORITY_LOW)
.setChannelId(Config.syncChannelId)
.setSmallIcon(R.drawable.ic_stat_cloud_download_black_24dp)
notificationManager.notify(1, notification.build())
notificationManager.notify(1, notification.build())
repository.handleDBActions()
val notifyNewItems = settings.getBoolean("notify_new_items", false)
db = Room.databaseBuilder(
applicationContext,
AppDatabase::class.java, "selfoss-database"
).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3)
.addMigrations(MIGRATION_3_4).build()
val actions = db.actionsDao().actions()
actions.forEach { action ->
when {
action.read -> doAndReportOnFail(
repository.markAsRead(action.articleId.toInt()),
action
)
action.unread -> doAndReportOnFail(
repository.unmarkAsRead(action.articleId.toInt()),
action
)
action.starred -> doAndReportOnFail(
repository.starr(action.articleId.toInt()),
action
)
action.unstarred -> doAndReportOnFail(
repository.unstarr(action.articleId.toInt()),
action
)
}
}
if (context.isNetworkAvailable()) {
launch {
try {
val newItems = repository.allItems(ItemType.UNREAD)
handleNewItemsNotification(newItems, notifyNewItems, notificationManager)
val readItems = repository.allItems(ItemType.ALL)
val starredItems = repository.allItems(ItemType.STARRED)
// TODO: save all to DB
} catch (e: Throwable) {}
}
val apiItems = repository.tryToCacheItemsAndGetNewOnes()
if (appSettingsService.isNotifyNewItemsEnabled()) {
launch {
handleNewItemsNotification(apiItems, notificationManager)
}
}
apiItems.map { it.preloadImages(context) }
}
}
return Result.success()
@ -113,15 +66,15 @@ override fun doWork(): Result {
private fun handleNewItemsNotification(
newItems: List<SelfossModel.Item>?,
notifyNewItems: Boolean,
notificationManager: NotificationManager
) {
// TODO: Check if this coroutine is actually required
CoroutineScope(Dispatchers.IO).launch {
val apiItems = newItems.orEmpty()
val newSize = apiItems.filter { it.unread }.size
if (notifyNewItems && newSize > 0) {
if (newSize > 0) {
val intent = Intent(context, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
@ -134,7 +87,7 @@ override fun doWork(): Result {
val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, pflags)
val newItemsNotification =
NotificationCompat.Builder(applicationContext, Config.newItemsChannelId)
NotificationCompat.Builder(applicationContext, AppSettingsService.newItemsChannelId)
.setContentTitle(context.getString(R.string.new_items_notification_title))
.setContentText(
context.getString(
@ -143,7 +96,7 @@ override fun doWork(): Result {
)
)
.setPriority(PRIORITY_DEFAULT)
.setChannelId(Config.newItemsChannelId)
.setChannelId(AppSettingsService.newItemsChannelId)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.setSmallIcon(R.drawable.ic_tab_fiber_new_black_24dp)
@ -152,19 +105,9 @@ override fun doWork(): Result {
notificationManager.notify(2, newItemsNotification.build())
}
}
apiItems.map { it.preloadImages(context) }
Timer("", false).schedule(4000) {
notificationManager.cancel(1)
}
}
}
private fun doAndReportOnFail(result: Boolean, action: ActionEntity) {
// TODO: Failures should be reported
if (result) {
thread {
db.actionsDao().delete(action)
}
}
}
}

View File

@ -8,6 +8,7 @@ import android.graphics.Typeface
import android.graphics.drawable.ColorDrawable
import android.net.Uri
import android.os.Bundle
import android.util.TypedValue
import android.view.*
import android.webkit.WebResourceResponse
import android.webkit.WebSettings
@ -15,36 +16,31 @@ import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.browser.customtabs.CustomTabsIntent
import androidx.core.content.res.ResourcesCompat
import androidx.core.widget.NestedScrollView
import androidx.fragment.app.Fragment
import androidx.room.Room
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.*
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_1_2
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_2_3
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_3_4
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
import bou.amine.apps.readerforselfossv2.android.utils.*
import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper
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.utils.glide.getBitmapInputStream
import bou.amine.apps.readerforselfossv2.android.utils.glide.loadMaybeBasicAuth
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable
import bou.amine.apps.readerforselfossv2.android.utils.openInBrowserAsNewTask
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.rest.SelfossModel
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
import bou.amine.apps.readerforselfossv2.utils.getThumbnail
import bou.amine.apps.readerforselfossv2.utils.isEmptyOrNullOrNullString
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions
import com.github.rubensousa.floatingtoolbar.FloatingToolbar
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.russhwolf.settings.Settings
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -52,18 +48,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.URL
import java.util.*
import java.util.concurrent.ExecutionException
class ArticleFragment : Fragment(), DIAware {
private var fontSize: Int = 16
private lateinit var item: SelfossModel.Item
private var mCustomTabActivityHelper: CustomTabActivityHelper? = null
private lateinit var url: String
private lateinit var contentText: String
private lateinit var contentSource: String
@ -71,44 +64,28 @@ class ArticleFragment : Fragment(), DIAware {
private lateinit var contentTitle: String
private lateinit var allImages : ArrayList<String>
private lateinit var fab: FloatingActionButton
private lateinit var appColors: AppColors
private lateinit var db: AppDatabase
private lateinit var textAlignment: String
private lateinit var config: Config
private var _binding: FragmentArticleBinding? = null
private val binding get() = _binding!!
override val di : DI by closestDI()
private val repository: Repository by instance()
private var settings = Settings()
private val appSettingsService: AppSettingsService by instance()
private var typeface: Typeface? = null
private var resId: Int = 0
private var font = ""
private var staticBar = false
override fun onStop() {
super.onStop()
if (mCustomTabActivityHelper != null) {
mCustomTabActivityHelper!!.unbindCustomTabsService(activity)
}
}
private val mercuryApi : MercuryApi by instance()
override fun onCreate(savedInstanceState: Bundle?) {
appColors = AppColors(requireActivity())
config = Config()
super.onCreate(savedInstanceState)
val pi: ParecelableItem = requireArguments().getParcelable(ARG_ITEMS)!!
item = pi.toModel()
db = Room.databaseBuilder(
requireContext(),
AppDatabase::class.java, "selfoss-database"
).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).addMigrations(MIGRATION_3_4).build()
}
override fun onCreateView(
@ -121,21 +98,20 @@ class ArticleFragment : Fragment(), DIAware {
url = item.getLinkDecoded()
contentText = item.content
contentTitle = item.getTitleDecoded()
contentTitle = item.title.getHtmlDecoded()
contentImage = item.getThumbnail(repository.baseUrl)
contentSource = item.sourceAndDateText(repository.dateUtils)
contentSource = item.sourceAndDateText()
allImages = item.getImages()
fontSize = settings.getString("reader_font_size", "16").toInt()
staticBar = settings.getBoolean("reader_static_bar", false)
fontSize = appSettingsService.getFontSize()
staticBar = appSettingsService.isStaticBarEnabled()
font = appSettingsService.getFont()
font = settings.getString("reader_font", "")
if (font.isNotEmpty()) {
resId = requireContext().resources.getIdentifier(font, "font", requireContext().packageName)
typeface = try {
ResourcesCompat.getFont(requireContext(), resId)!!
} catch (e: java.lang.Exception) {
// ACRA.getErrorReporter().maybeHandleSilentException(Throwable("Font loading issue: ${e.message}"), requireContext())
// Just to be sure
null
}
@ -145,31 +121,25 @@ class ArticleFragment : Fragment(), DIAware {
fab = binding.fab
fab.backgroundTintList = ColorStateList.valueOf(appColors.colorAccent)
fab.backgroundTintList = ColorStateList.valueOf(resources.getColor(R.color.colorAccent))
fab.rippleColor = appColors.colorAccentDark
fab.rippleColor = resources.getColor(R.color.colorAccentDark)
val floatingToolbar: FloatingToolbar = binding.floatingToolbar
floatingToolbar.attachFab(fab)
floatingToolbar.background = ColorDrawable(appColors.colorAccent)
val customTabsIntent = requireActivity().buildCustomTabsIntent()
mCustomTabActivityHelper = CustomTabActivityHelper()
mCustomTabActivityHelper!!.bindCustomTabsService(activity)
floatingToolbar.background = ColorDrawable(resources.getColor(R.color.colorAccent))
floatingToolbar.setClickListener(
object : FloatingToolbar.ItemClickListener {
override fun onItemClick(item: MenuItem) {
when (item.itemId) {
R.id.more_action -> getContentFromMercury(customTabsIntent)
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.id)
repository.markAsRead(this@ArticleFragment.item)
}
this@ArticleFragment.item.unread = false
Toast.makeText(
@ -179,7 +149,7 @@ class ArticleFragment : Fragment(), DIAware {
).show()
} else {
CoroutineScope(Dispatchers.IO).launch {
repository.unmarkAsRead(this@ArticleFragment.item.id)
repository.unmarkAsRead(this@ArticleFragment.item)
}
this@ArticleFragment.item.unread = true
Toast.makeText(
@ -209,7 +179,7 @@ class ArticleFragment : Fragment(), DIAware {
}
if (contentText.isEmptyOrNullOrNullString()) {
getContentFromMercury(customTabsIntent)
getContentFromMercury()
} else {
binding.titleView.text = contentTitle
if (typeface != null) {
@ -223,7 +193,7 @@ class ArticleFragment : Fragment(), DIAware {
Glide
.with(requireContext())
.asBitmap()
.loadMaybeBasicAuth(config, contentImage)
.load(contentImage)
.apply(RequestOptions.fitCenterTransform())
.into(binding.imageView)
} else {
@ -252,7 +222,7 @@ class ArticleFragment : Fragment(), DIAware {
.setTitle(requireContext().getString(R.string.webview_dialog_issue_title))
.setPositiveButton(android.R.string.ok
) { _, _ ->
settings.putBoolean("prefer_article_viewer", false)
appSettingsService.disableArticleViewer()
requireActivity().finish()
}
.create()
@ -268,101 +238,93 @@ class ArticleFragment : Fragment(), DIAware {
}
private fun refreshAlignment() {
textAlignment = when (settings.getInt("text_align", 1)) {
textAlignment = when (appSettingsService.getActiveAllignment()) {
1 -> "justify"
2 -> "left"
else -> "justify"
}
}
private fun getContentFromMercury(customTabsIntent: CustomTabsIntent) {
if ((context != null && requireContext().isNetworkAvailable(null)) || context == null) {
private fun getContentFromMercury() {
if (repository.isNetworkAvailable()) {
binding.progressBar.visibility = View.VISIBLE
val parser = MercuryApi()
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()) {
CoroutineScope(Dispatchers.Main).launch {
val response = mercuryApi.query(url)
if (response.success) {
try {
if (response.data != null && response.data!!.content != null && !response.data!!.content.isNullOrEmpty()) {
try {
binding.titleView.text = response.data!!.title
if (typeface != null) {
binding.titleView.typeface = typeface
}
try {
binding.titleView.text = response.body()!!.title
if (typeface != null) {
binding.titleView.typeface = typeface
}
// Note: Mercury may return relative urls... If it does the url val will not be changed.
URL(response.data!!.url)
url = response.data!!.url
} catch (e: MalformedURLException) {
// Mercury returned a relative url. We do nothing.
}
} catch (e: Exception) {
}
try {
contentText = response.data!!.content.orEmpty()
htmlToWebview()
} catch (e: Exception) {
}
try {
if (response.data!!.lead_image_url != null && !response.data!!.lead_image_url.isNullOrEmpty() && context != null) {
binding.imageView.visibility = View.VISIBLE
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.
Glide
.with(requireContext())
.asBitmap()
.load(
response.data!!.lead_image_url.orEmpty()
)
.apply(RequestOptions.fitCenterTransform())
.into(binding.imageView)
} catch (e: Exception) {
}
} catch (e: Exception) {
} else {
binding.imageView.visibility = View.GONE
}
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()
.loadMaybeBasicAuth(config, 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(customTabsIntent)
} catch (e: Exception) {
if (context != null) {
}
} catch (e: Exception) {
if (context != null) {
}
}
} 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) {
}
}
override fun onFailure(
call: Call<ParsedContent>,
t: Throwable
) = openInBrowserAfterFailing(customTabsIntent)
} else {
openInBrowserAfterFailing()
}
)
}
}
}
private fun htmlToWebview() {
val stringColor = String.format("#%06X", 0xFFFFFF and appColors.colorAccent)
val attrs: IntArray = intArrayOf(android.R.attr.fontFamily)
val a: TypedArray = requireContext().obtainStyledAttributes(resId, attrs)
@ -371,18 +333,18 @@ class ArticleFragment : Fragment(), DIAware {
binding.webcontent.settings.standardFontFamily = a.getString(0)
binding.webcontent.visibility = View.VISIBLE
// TODO: Set the color strings programmatically
val (stringTextColor, stringBackgroundColor) = if (appColors.isDarkTheme) {
Pair("#FFFFFF", "#303030")
} else {
Pair("#212121", "#FAFAFA")
}
val colorOnSurface = TypedValue()
requireContext().theme.resolveAttribute(R.attr.colorOnSurface, colorOnSurface, true)
val colorSurface = TypedValue()
requireContext().theme.resolveAttribute(R.attr.colorSurface, colorSurface, true)
binding.webcontent.settings.useWideViewPort = true
binding.webcontent.settings.loadWithOverviewMode = true
binding.webcontent.settings.javaScriptEnabled = false
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)))
@ -390,6 +352,7 @@ class ArticleFragment : Fragment(), DIAware {
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")) {
@ -416,7 +379,7 @@ class ArticleFragment : Fragment(), DIAware {
}
val gestureDetector = GestureDetector(activity, object : GestureDetector.SimpleOnGestureListener() {
override fun onSingleTapUp(e: MotionEvent?): Boolean {
override fun onSingleTapUp(e: MotionEvent): Boolean {
return performClick()
}
})
@ -437,6 +400,7 @@ class ArticleFragment : Fragment(), DIAware {
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 -> ""
}
@ -465,10 +429,10 @@ class ArticleFragment : Fragment(), DIAware {
| max-width: 100%;
| }
| a {
| color: $stringColor !important;
| color: ${String.format("#%06X", 0xFFFFFF and resources.getColor(R.color.colorAccent))} !important;
| }
| *:not(a) {
| color: $stringTextColor;
| color: ${String.format("#%06X", 0xFFFFFF and colorOnSurface.data)};
| }
| * {
| font-size: ${fontSize}px;
@ -476,11 +440,11 @@ class ArticleFragment : Fragment(), DIAware {
| word-break: break-word;
| overflow:hidden;
| line-height: 1.5em;
| background-color: $stringBackgroundColor;
| background-color: ${String.format("#%06X", 0xFFFFFF and colorSurface.data)};
| }
| body, html {
| background-color: $stringBackgroundColor !important;
| border-color: $stringBackgroundColor !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;
| }
@ -490,7 +454,7 @@ class ArticleFragment : Fragment(), DIAware {
| pre, code {
| white-space: pre-wrap;
| width:100%;
| background-color: $stringBackgroundColor;
| background-color: ${String.format("#%06X", 0xFFFFFF and colorSurface.data)};
| }
| </style>
| $fontLinkAndStyle
@ -514,13 +478,9 @@ class ArticleFragment : Fragment(), DIAware {
binding.nestedScrollView.smoothScrollBy(0, -height/2)
}
private fun openInBrowserAfterFailing(customTabsIntent: CustomTabsIntent) {
private fun openInBrowserAfterFailing() {
binding.progressBar.visibility = View.GONE
requireActivity().openItemUrlInternalBrowser(
url,
customTabsIntent,
requireActivity()
)
requireActivity().openInBrowserAsNewTask(this@ArticleFragment.item)
}
companion object {

View File

@ -1,7 +1,9 @@
package bou.amine.apps.readerforselfossv2.android.fragments
import android.os.Bundle
import android.view.*
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import bou.amine.apps.readerforselfossv2.android.databinding.FragmentImageBinding
import com.bumptech.glide.Glide

View File

@ -1,43 +1,12 @@
package bou.amine.apps.readerforselfossv2.android.model
import android.content.Context
import android.net.Uri
import android.text.Html
import android.webkit.URLUtil
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.utils.getImages
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions
import org.jsoup.Jsoup
import java.util.*
/**
* Items extension methods
*/
fun SelfossModel.Item.getIcon(baseUrl: String): String {
return constructUrl(baseUrl, "favicons", icon)
}
fun SelfossModel.Item.getThumbnail(baseUrl: String): String {
return constructUrl(baseUrl, "thumbnails", thumbnail)
}
fun SelfossModel.Item.getImages() : ArrayList<String> {
val allImages = ArrayList<String>()
for ( image in Jsoup.parse(content).getElementsByTag("img")) {
val url = image.attr("src")
if (url.lowercase(Locale.US).contains(".jpg") ||
url.lowercase(Locale.US).contains(".jpeg") ||
url.lowercase(Locale.US).contains(".png") ||
url.lowercase(Locale.US).contains(".webp"))
{
allImages.add(url)
}
}
return allImages
}
fun SelfossModel.Item.preloadImages(context: Context) : Boolean {
val imageUrls = this.getImages()
@ -60,66 +29,14 @@ fun SelfossModel.Item.preloadImages(context: Context) : Boolean {
return true
}
fun SelfossModel.Item.getTitleDecoded(): String {
return Html.fromHtml(title).toString()
}
fun SelfossModel.Item.getSourceTitle(): String {
return Html.fromHtml(sourcetitle).toString()
}
// TODO: maybe find a better way to handle these kind of urls
fun SelfossModel.Item.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;", "&")
fun String.toTextDrawableString(): String {
val textDrawable = StringBuilder()
for (s in this.split(" ".toRegex()).filter { it.isNotEmpty() }.toTypedArray()) {
try {
textDrawable.append(s[0])
} catch (e: StringIndexOutOfBoundsException) {
// We do nothing
}
// handle :443 => https
if (stringUrl.contains(":443")) {
stringUrl = stringUrl.replace(":443", "").replace("http://", "https://")
}
// handle url not starting with http
if (stringUrl.startsWith("//")) {
stringUrl = "http:$stringUrl"
}
return stringUrl
}
/**
* Sources extension methods
*/
fun SelfossModel.Source.getIcon(baseUrl: String): String {
return constructUrl(baseUrl, "favicons", icon)
}
fun SelfossModel.Source.getTitleDecoded(): String {
return Html.fromHtml(title).toString()
}
/**
* Common methods
*/
private fun constructUrl(baseUrl: String, path: String, file: String?): String {
return if (file == null || file == "null" || file.isEmpty()) {
""
} else {
val baseUriBuilder = Uri.parse(baseUrl).buildUpon()
baseUriBuilder.appendPath(path).appendPath(file)
baseUriBuilder.toString()
}
return textDrawable.toString()
}

View File

@ -1,11 +1,8 @@
package bou.amine.apps.readerforselfossv2.android.model
import android.os.Build
import android.os.Parcel
import android.os.Parcelable
import androidx.annotation.RequiresApi
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
import com.google.gson.annotations.SerializedName
import bou.amine.apps.readerforselfossv2.model.SelfossModel
fun SelfossModel.Item.toParcelable() : ParecelableItem =
ParecelableItem(
@ -36,17 +33,17 @@ fun ParecelableItem.toModel() : SelfossModel.Item =
this.tags.split(",")
)
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
) : Parcelable {
companion object {

View File

@ -1,28 +0,0 @@
package bou.amine.apps.readerforselfossv2.android.persistence
import android.content.Context
import androidx.room.Room
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
import bou.amine.apps.readerforselfossv2.android.persistence.entities.AndroidItemEntity
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_1_2
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_2_3
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_3_4
import bou.amine.apps.readerforselfossv2.dao.DeviceDatabase
class AndroidDeviceDatabase(applicationContext: Context): DeviceDatabase<AndroidItemEntity> {
var db: AppDatabase = Room.databaseBuilder(
applicationContext,
AppDatabase::class.java, "selfoss-database"
).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).addMigrations(MIGRATION_3_4).build()
override suspend fun items(): List<AndroidItemEntity> = db.itemsDao().items()
override suspend fun insertAllItems(vararg items: AndroidItemEntity) = db.itemsDao().insertAllItems(*items)
override suspend fun deleteAllItems() = db.itemsDao().deleteAllItems()
override suspend fun delete(item: AndroidItemEntity) = db.itemsDao().delete(item)
override suspend fun updateItem(item: AndroidItemEntity) = db.itemsDao().updateItem(item)
}

View File

@ -1,40 +0,0 @@
package bou.amine.apps.readerforselfossv2.android.persistence
import bou.amine.apps.readerforselfossv2.android.persistence.entities.AndroidItemEntity
import bou.amine.apps.readerforselfossv2.android.utils.persistence.toEntity
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
import bou.amine.apps.readerforselfossv2.service.DeviceDataBaseService
import bou.amine.apps.readerforselfossv2.service.SearchService
class AndroidDeviceDatabaseService(db: AndroidDeviceDatabase, searchService: SearchService) :
DeviceDataBaseService<AndroidItemEntity>(db, searchService) {
override suspend fun updateDatabase() {
if (itemsCaching) {
if (items.isEmpty()) {
getFromDB()
}
db.deleteAllItems()
db.insertAllItems(*(items.map { it.toEntity() }).toTypedArray())
}
}
override suspend fun clearDBItems() {
db.deleteAllItems()
}
override fun appendNewItems(newItems: List<SelfossModel.Item>) {
var oldItems = items
if (oldItems != newItems) {
oldItems = oldItems.filter { item -> newItems.find { it.id == item.id } == null } as ArrayList<SelfossModel.Item>
oldItems.addAll(newItems)
items = oldItems
sortItems()
getFocusedItems()
}
}
override fun getFromDB() {
TODO("Not yet implemented")
}
}

View File

@ -1,23 +0,0 @@
package bou.amine.apps.readerforselfossv2.android.persistence.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import bou.amine.apps.readerforselfossv2.android.persistence.entities.ActionEntity
@Dao
interface ActionsDao {
@Query("SELECT * FROM actions order by id asc")
suspend fun actions(): List<ActionEntity>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAllActions(vararg actions: ActionEntity)
@Query("DELETE FROM actions WHERE articleid = :article_id AND read = 1")
fun deleteReadActionForArticle(article_id: String)
@Delete
fun delete(action: ActionEntity)
}

View File

@ -1,36 +0,0 @@
package bou.amine.apps.readerforselfossv2.android.persistence.dao
import androidx.room.Delete
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import bou.amine.apps.readerforselfossv2.android.persistence.entities.SourceEntity
import bou.amine.apps.readerforselfossv2.android.persistence.entities.TagEntity
@Dao
interface DrawerDataDao {
@Query("SELECT * FROM tags")
fun tags(): List<TagEntity>
@Query("SELECT * FROM sources")
fun sources(): List<SourceEntity>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAllTags(vararg tags: TagEntity)
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAllSources(vararg sources: SourceEntity)
@Query("DELETE FROM tags")
fun deleteAllTags()
@Query("DELETE FROM sources")
fun deleteAllSources()
@Delete
fun deleteTag(tag: TagEntity)
@Delete
fun deleteSource(source: SourceEntity)
}

View File

@ -1,29 +0,0 @@
package bou.amine.apps.readerforselfossv2.android.persistence.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import bou.amine.apps.readerforselfossv2.android.persistence.entities.AndroidItemEntity
import androidx.room.Update
@Dao
interface ItemsDao {
@Query("SELECT * FROM items order by id desc")
suspend fun items(): List<AndroidItemEntity>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAllItems(vararg items: AndroidItemEntity)
@Query("DELETE FROM items")
suspend fun deleteAllItems()
@Delete
suspend fun delete(item: AndroidItemEntity)
@Update
suspend fun updateItem(item: AndroidItemEntity)
}

View File

@ -1,20 +0,0 @@
package bou.amine.apps.readerforselfossv2.android.persistence.database
import androidx.room.RoomDatabase
import androidx.room.Database
import bou.amine.apps.readerforselfossv2.android.persistence.dao.ActionsDao
import bou.amine.apps.readerforselfossv2.android.persistence.dao.DrawerDataDao
import bou.amine.apps.readerforselfossv2.android.persistence.dao.ItemsDao
import bou.amine.apps.readerforselfossv2.android.persistence.entities.ActionEntity
import bou.amine.apps.readerforselfossv2.android.persistence.entities.AndroidItemEntity
import bou.amine.apps.readerforselfossv2.android.persistence.entities.SourceEntity
import bou.amine.apps.readerforselfossv2.android.persistence.entities.TagEntity
@Database(entities = [TagEntity::class, SourceEntity::class, AndroidItemEntity::class, ActionEntity::class], version = 4)
abstract class AppDatabase : RoomDatabase() {
abstract fun drawerDataDao(): DrawerDataDao
abstract fun itemsDao(): ItemsDao
abstract fun actionsDao(): ActionsDao
}

View File

@ -1,22 +0,0 @@
package bou.amine.apps.readerforselfossv2.android.persistence.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "actions")
data class ActionEntity(
@ColumnInfo(name = "articleid")
val articleId: String,
@ColumnInfo(name = "read")
val read: Boolean,
@ColumnInfo(name = "unread")
val unread: Boolean,
@ColumnInfo(name = "starred")
var starred: Boolean,
@ColumnInfo(name = "unstarred")
var unstarred: Boolean
) {
@PrimaryKey(autoGenerate = true)
var id: Int = 0
}

View File

@ -1,33 +0,0 @@
package bou.amine.apps.readerforselfossv2.android.persistence.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
@Entity(tableName = "items")
data class AndroidItemEntity(
@PrimaryKey
@ColumnInfo(name = "id")
val id: String,
@ColumnInfo(name = "datetime")
val datetime: String,
@ColumnInfo(name = "title")
val title: String,
@ColumnInfo(name = "content")
val content: String,
@ColumnInfo(name = "unread")
val unread: Boolean,
@ColumnInfo(name = "starred")
var starred: Boolean,
@ColumnInfo(name = "thumbnail")
val thumbnail: String?,
@ColumnInfo(name = "icon")
val icon: String?,
@ColumnInfo(name = "link")
val link: String,
@ColumnInfo(name = "sourcetitle")
val sourcetitle: String,
@ColumnInfo(name = "tags")
val tags: String
)

View File

@ -1,33 +0,0 @@
package bou.amine.apps.readerforselfossv2.android.persistence.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "tags")
data class TagEntity(
@PrimaryKey
@ColumnInfo(name = "tag")
val tag: String,
@ColumnInfo(name = "color")
val color: String,
@ColumnInfo(name = "unread")
val unread: Int
)
@Entity(tableName = "sources")
data class SourceEntity(
@PrimaryKey
@ColumnInfo(name = "id")
val id: String,
@ColumnInfo(name = "title")
val title: String,
@ColumnInfo(name = "tags")
val tags: String,
@ColumnInfo(name = "spout")
val spout: String,
@ColumnInfo(name = "error")
val error: String,
@ColumnInfo(name = "icon")
val icon: String
)

View File

@ -1,34 +0,0 @@
package bou.amine.apps.readerforselfossv2.android.persistence.migrations
import androidx.sqlite.db.SupportSQLiteDatabase
import androidx.room.migration.Migration
val MIGRATION_1_2: Migration = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE IF NOT EXISTS `items` (`id` TEXT NOT NULL, `datetime` TEXT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `thumbnail` TEXT NOT NULL, `icon` TEXT NOT NULL, `link` TEXT NOT NULL, `sourcetitle` TEXT NOT NULL, `tags` TEXT NOT NULL, PRIMARY KEY(`id`))")
}
}
val MIGRATION_2_3: Migration = object : Migration(2, 3) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE IF NOT EXISTS `actions` (`id` INTEGER NOT NULL, `articleid` TEXT NOT NULL, `read` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `unstarred` INTEGER NOT NULL, PRIMARY KEY(`id`))")
}
}
val MIGRATION_3_4: Migration = object : Migration(3, 4) {
override fun migrate(database: SupportSQLiteDatabase) {
// @see https://stackoverflow.com/questions/57392015/how-to-migrate-not-null-table-column-into-null-in-android-room-database
// Create the new table
database.execSQL("CREATE TABLE IF NOT EXISTS `itemstmp` (`id` TEXT NOT NULL, `datetime` TEXT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `thumbnail` TEXT, `icon` TEXT, `link` TEXT NOT NULL, `sourcetitle` TEXT NOT NULL, `tags` TEXT NOT NULL, PRIMARY KEY(`id`))")
// Copy the data
database.execSQL(
"INSERT INTO itemstmp (`id`, `datetime`, `title`, `content`, `unread`, `starred`, `thumbnail`, `icon`, `link`, `sourcetitle`, `tags`) SELECT `id`, `datetime`, `title`, `content`, `unread`, `starred`, `thumbnail`, `icon`, `link`, `sourcetitle`, `tags` FROM items")
// Remove the old table
database.execSQL("DROP TABLE items")
// Change the table name to the correct one
database.execSQL("ALTER TABLE itemstmp RENAME TO items")
}
}

View File

@ -3,22 +3,20 @@ package bou.amine.apps.readerforselfossv2.android.settings
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.text.*
import androidx.preference.EditTextPreference
import androidx.preference.PreferenceManager
import android.view.*
import android.text.Editable
import android.text.InputFilter
import android.text.InputType
import android.text.TextWatcher
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.widget.addTextChangedListener
import androidx.preference.EditTextPreference
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.themes.Toppings
import bou.amine.apps.readerforselfossv2.android.utils.Config
import com.ftinc.scoop.Scoop
import com.russhwolf.settings.Settings
import java.lang.NumberFormatException
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
private const val TITLE_TAG = "settingsActivityTitle"
@ -27,15 +25,8 @@ class SettingsActivity : AppCompatActivity(),
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean("dark_theme", false)) {
setTheme(R.style.NoBarDark)
}
val binding = ActivitySettingsBinding.inflate(layoutInflater)
val scoop = Scoop.getInstance()
scoop.bind(this, Toppings.PRIMARY.value, binding.toolbar)
scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.value)
setContentView(binding.root)
if (savedInstanceState == null) {
supportFragmentManager
@ -65,11 +56,13 @@ class SettingsActivity : AppCompatActivity(),
}
override fun onSupportNavigateUp(): Boolean {
if (supportFragmentManager.popBackStackImmediate()) {
return if (supportFragmentManager.popBackStackImmediate()) {
supportActionBar?.title = getText(R.string.title_activity_settings)
return true
false
} else {
super.onBackPressed()
true
}
return super.onSupportNavigateUp()
}
override fun onPreferenceStartFragment(
@ -163,26 +156,11 @@ class SettingsActivity : AppCompatActivity(),
class ThemePreferenceFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.pref_theme, rootKey)
setHasOptionsMenu(true)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.settings_theme, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val id = item.itemId
if (id == R.id.clear) {
val settings = Settings()
settings.remove("color_primary")
settings.remove("color_primary_dark")
settings.remove("color_accent")
settings.remove("color_accent_dark")
settings.remove("dark_theme")
requireActivity().recreate()
preferenceManager.findPreference<Preference>("currentMode")?.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
AppCompatDelegate.setDefaultNightMode(newValue.toString().toInt()) // ListPreference Only takes string-arrays ¯\_(ツ)_/¯
true
}
return super.onOptionsItemSelected(item)
}
}
@ -196,17 +174,17 @@ class SettingsActivity : AppCompatActivity(),
setPreferencesFromResource(R.xml.pref_links, rootKey)
preferenceManager.findPreference<Preference>("trackerLink")?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
openUrl(Uri.parse(Config.trackerUrl))
openUrl(Uri.parse(AppSettingsService.trackerUrl))
true
}
preferenceManager.findPreference<Preference>("sourceLink")?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
openUrl(Uri.parse(Config.sourceUrl))
openUrl(Uri.parse(AppSettingsService.sourceUrl))
false
}
preferenceManager.findPreference<Preference>("translation")?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
openUrl(Uri.parse(Config.translationUrl))
openUrl(Uri.parse(AppSettingsService.translationUrl))
false
}
}

View File

@ -1,61 +0,0 @@
package bou.amine.apps.readerforselfossv2.android.themes
import android.app.Activity
import androidx.annotation.ColorInt
import bou.amine.apps.readerforselfossv2.android.R
import com.russhwolf.settings.Settings
class AppColors(a: Activity) {
@ColorInt val colorPrimary: Int
@ColorInt val colorPrimaryDark: Int
@ColorInt val colorAccent: Int
@ColorInt val colorAccentDark: Int
@ColorInt val colorBackground: Int
@ColorInt val textColor: Int
val isDarkTheme: Boolean
init {
val settings = Settings()
colorPrimary =
settings.getInt(
"color_primary",
a.resources.getColor(R.color.colorPrimary)
)
colorPrimaryDark =
settings.getInt(
"color_primary_dark",
a.resources.getColor(R.color.colorPrimaryDark)
)
colorAccent =
settings.getInt(
"color_accent",
a.resources.getColor(R.color.colorAccent)
)
colorAccentDark =
settings.getInt(
"color_accent_dark",
a.resources.getColor(R.color.colorAccentDark)
)
isDarkTheme =
settings.getBoolean(
"dark_theme",
false
)
colorBackground = if (isDarkTheme) {
a.setTheme(R.style.NoBarDark)
R.color.darkBackground
} else {
a.setTheme(R.style.NoBar)
R.color.grey_50
}
textColor = if (isDarkTheme) {
R.color.white
} else {
R.color.grey_900
}
}
}

View File

@ -1,8 +0,0 @@
package bou.amine.apps.readerforselfossv2.android.themes
enum class Toppings(val value: Int) {
PRIMARY(1),
PRIMARY_DARK(2),
ACCENT(3),
ACCENT_DARK(4)
}

View File

@ -1,62 +0,0 @@
package bou.amine.apps.readerforselfossv2.android.utils
import android.app.Activity
import android.content.Context
import android.content.Intent
import bou.amine.apps.readerforselfossv2.android.LoginActivity
import com.russhwolf.settings.Settings
class Config {
val settings = Settings()
val baseUrl: String
get() = settings.getString("url", "")
val userLogin: String
get() = settings.getString("login", "")
val userPassword: String
get() = settings.getString("password", "")
val httpUserLogin: String
get() = settings.getString("httpUserName", "")
val httpUserPassword: String
get() = settings.getString("httpPassword", "")
companion object {
const val settingsName = "paramsselfoss"
const val feedbackEmail = "aminecmi@gmail.com"
const val translationUrl = "https://crwd.in/readerforselfoss"
const val sourceUrl = "https://github.com/aminecmi/ReaderforSelfoss"
const val trackerUrl = "https://github.com/aminecmi/ReaderforSelfoss/issues"
const val syncChannelId = "sync-channel-id"
const val newItemsChannelId = "new-items-channel-id"
var apiVersion = 0
/* Execute logout and clear all settings to default */
fun logoutAndRedirect(
c: Context,
callingActivity: Activity,
baseUrlFail: Boolean = false
): Boolean {
val settings = Settings()
settings.clear()
val intent = Intent(c, LoginActivity::class.java)
if (baseUrlFail) {
intent.putExtra("baseUrlFail", baseUrlFail)
}
c.startActivity(intent)
callingActivity.finish()
return true
}
}
}

View File

@ -1,43 +0,0 @@
package bou.amine.apps.readerforselfossv2.android.utils
import okhttp3.OkHttpClient
import java.security.cert.CertificateException
import java.security.cert.X509Certificate
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManager
import javax.net.ssl.X509TrustManager
fun getUnsafeHttpClient(): OkHttpClient.Builder =
try {
// Create a trust manager that does not validate certificate chains
val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager {
override fun getAcceptedIssuers(): Array<X509Certificate> =
arrayOf()
@Throws(CertificateException::class)
override fun checkClientTrusted(
chain: Array<java.security.cert.X509Certificate>,
authType: String
) {
}
@Throws(CertificateException::class)
override fun checkServerTrusted(
chain: Array<java.security.cert.X509Certificate>,
authType: String
) {
}
})
// Install the all-trusting trust manager
val sslContext = SSLContext.getInstance("SSL")
sslContext.init(null, trustAllCerts, java.security.SecureRandom())
val sslSocketFactory = sslContext.socketFactory
OkHttpClient.Builder()
.sslSocketFactory(sslSocketFactory, trustAllCerts[0] as X509TrustManager)
.hostnameVerifier { _, _ -> true }
} catch (e: Exception) {
throw RuntimeException(e)
}

View File

@ -1,29 +0,0 @@
package bou.amine.apps.readerforselfossv2.android.utils
import android.content.Context
import bou.amine.apps.readerforselfossv2.android.model.getSourceTitle
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
import bou.amine.apps.readerforselfossv2.utils.DateUtils
import bou.amine.apps.readerforselfossv2.utils.parseRelativeDate
fun String.toTextDrawableString(c: Context): String {
val textDrawable = StringBuilder()
for (s in this.split(" ".toRegex()).filter { it.isNotEmpty() }.toTypedArray()) {
try {
textDrawable.append(s[0])
} catch (e: StringIndexOutOfBoundsException) {
}
}
return textDrawable.toString()
}
fun SelfossModel.Item.sourceAndDateText(dateUtils: DateUtils): String {
val formattedDate = parseRelativeDate(dateUtils)
return getSourceTitle() + formattedDate
}
fun SelfossModel.Item.toggleStar(): SelfossModel.Item {
this.starred = !this.starred
return this
}

View File

@ -15,110 +15,16 @@ import android.view.MotionEvent
import android.view.View
import android.widget.TextView
import android.widget.Toast
import androidx.browser.customtabs.CustomTabsIntent
import bou.amine.apps.readerforselfossv2.android.R
import bou.amine.apps.readerforselfossv2.android.ReaderActivity
import bou.amine.apps.readerforselfossv2.android.model.getLinkDecoded
import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.utils.toStringUriWithHttp
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
fun Context.buildCustomTabsIntent(): CustomTabsIntent {
val actionIntent = Intent(Intent.ACTION_SEND)
actionIntent.type = "text/plain"
val pflags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_IMMUTABLE
} else {
0
}
val createPendingShareIntent: PendingIntent = PendingIntent.getActivity(
this,
0,
actionIntent,
pflags
)
val intentBuilder = CustomTabsIntent.Builder()
// TODO: change to primary when it's possible to customize custom tabs title color
//intentBuilder.setToolbarColor(c.getResources().getColor(R.color.colorPrimary));
intentBuilder.setToolbarColor(resources.getColor(R.color.colorAccentDark))
intentBuilder.setShowTitle(true)
intentBuilder.setStartAnimations(
this,
R.anim.slide_in_right,
R.anim.slide_out_left
)
intentBuilder.setExitAnimations(
this,
android.R.anim.slide_in_left,
android.R.anim.slide_out_right
)
val closeicon = BitmapFactory.decodeResource(resources, R.drawable.ic_close_white_24dp)
intentBuilder.setCloseButtonIcon(closeicon)
val shareLabel = this.getString(R.string.label_share)
val icon = BitmapFactory.decodeResource(
resources,
R.drawable.ic_share_white_24dp
)
intentBuilder.setActionButton(icon, shareLabel, createPendingShareIntent)
return intentBuilder.build()
}
fun Context.openItemUrlInternally(
allItems: ArrayList<SelfossModel.Item>,
currentItem: Int,
linkDecoded: String,
customTabsIntent: CustomTabsIntent,
articleViewer: Boolean,
app: Activity
) {
if (articleViewer) {
ReaderActivity.allItems = allItems
val intent = Intent(this, ReaderActivity::class.java)
intent.putExtra("currentItem", currentItem)
app.startActivity(intent)
} else {
this.openItemUrlInternalBrowser(
linkDecoded,
customTabsIntent,
app)
}
}
fun Context.openItemUrlInternalBrowser(
linkDecoded: String,
customTabsIntent: CustomTabsIntent,
app: Activity
) {
try {
CustomTabActivityHelper.openCustomTab(
app,
customTabsIntent,
Uri.parse(linkDecoded)
) { _, uri ->
val intent = Intent(Intent.ACTION_VIEW, uri)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
startActivity(intent)
}
} catch (e: Exception) {
openInBrowser(linkDecoded, app)
}
}
fun Context.openItemUrl(
allItems: ArrayList<SelfossModel.Item>,
currentItem: Int,
linkDecoded: String,
customTabsIntent: CustomTabsIntent,
internalBrowser: Boolean,
articleViewer: Boolean,
app: Activity
) {
@ -130,41 +36,24 @@ fun Context.openItemUrl(
Toast.LENGTH_LONG
).show()
} else {
if (!internalBrowser) {
openInBrowser(linkDecoded, app)
} else if (articleViewer) {
this.openItemUrlInternally(
allItems,
currentItem,
linkDecoded,
customTabsIntent,
articleViewer,
app
)
if (articleViewer) {
ReaderActivity.allItems = allItems
val intent = Intent(this, ReaderActivity::class.java)
intent.putExtra("currentItem", currentItem)
app.startActivity(intent)
} else {
this.openItemUrlInternalBrowser(
linkDecoded,
customTabsIntent,
app
)
val intent = Intent(Intent.ACTION_VIEW)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
intent.data = Uri.parse(linkDecoded.toStringUriWithHttp())
startActivity(intent)
}
}
}
private fun openInBrowser(linkDecoded: String, app: Activity) {
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse(linkDecoded)
try {
app.startActivity(intent)
} catch (e: ActivityNotFoundException) {
Toast.makeText(app.baseContext, e.message, Toast.LENGTH_LONG).show()
}
}
fun String.isUrlValid(): Boolean =
this.toHttpUrlOrNull() != null && Patterns.WEB_URL.matcher(this).matches()
fun String.isBaseUrlValid(ctx: Context): Boolean {
fun String.isBaseUrlInvalid(): Boolean {
val baseUrl = this.toHttpUrlOrNull()
var existsAndEndsWithSlash = false
if (baseUrl != null) {
@ -172,7 +61,7 @@ fun String.isBaseUrlValid(ctx: Context): Boolean {
existsAndEndsWithSlash = "" == pathSegments[pathSegments.size - 1]
}
return Patterns.WEB_URL.matcher(this).matches() && existsAndEndsWithSlash
return !(Patterns.WEB_URL.matcher(this).matches() && existsAndEndsWithSlash)
}
fun Context.openInBrowserAsNewTask(i: SelfossModel.Item) {
@ -182,7 +71,7 @@ fun Context.openInBrowserAsNewTask(i: SelfossModel.Item) {
startActivity(intent)
}
class LinkOnTouchListener: View.OnTouchListener {
class LinkOnTouchListener : View.OnTouchListener {
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
var ret = false
val widget: TextView = v as TextView
@ -192,7 +81,8 @@ class LinkOnTouchListener: View.OnTouchListener {
val action = event!!.action
if (action == MotionEvent.ACTION_UP ||
action == MotionEvent.ACTION_DOWN) {
action == MotionEvent.ACTION_DOWN
) {
var x: Float = event.x
var y: Float = event.y

View File

@ -1,9 +0,0 @@
package bou.amine.apps.readerforselfossv2.android.utils
import android.content.res.Resources
val Int.toPx: Int
get() = (this * Resources.getSystem().displayMetrics.density).toInt()
val Int.toDp: Int
get() = (this / Resources.getSystem().displayMetrics.density).toInt()

View File

@ -1,153 +0,0 @@
package bou.amine.apps.readerforselfossv2.android.utils.customtabs;
import android.app.Activity;
import android.net.Uri;
import android.os.Bundle;
import androidx.browser.customtabs.CustomTabsClient;
import androidx.browser.customtabs.CustomTabsIntent;
import androidx.browser.customtabs.CustomTabsServiceConnection;
import androidx.browser.customtabs.CustomTabsSession;
import java.util.List;
/**
* This is a helper class to manage the connection to the Custom Tabs Service.
*/
public class CustomTabActivityHelper implements ServiceConnectionCallback {
private CustomTabsSession mCustomTabsSession;
private CustomTabsClient mClient;
private CustomTabsServiceConnection mConnection;
private ConnectionCallback mConnectionCallback;
/**
* Opens the URL on a Custom Tab if possible. Otherwise fallsback to opening it on a WebView.
*
* @param activity The host activity.
* @param customTabsIntent a CustomTabsIntent to be used if Custom Tabs is available.
* @param uri the Uri to be opened.
* @param fallback a CustomTabFallback to be used if Custom Tabs is not available.
*/
public static void openCustomTab(Activity activity,
CustomTabsIntent customTabsIntent,
Uri uri,
CustomTabFallback fallback) {
String packageName = CustomTabsHelper.getPackageNameToUse(activity);
//If we cant find a package name, it means theres no browser that supports
//Chrome Custom Tabs installed. So, we fallback to the webview
if (packageName == null) {
if (fallback != null) {
fallback.openUri(activity, uri);
}
} else {
customTabsIntent.intent.setPackage(packageName);
customTabsIntent.launchUrl(activity, uri);
}
}
/**
* Unbinds the Activity from the Custom Tabs Service.
*
* @param activity the activity that is connected to the service.
*/
public void unbindCustomTabsService(Activity activity) {
if (mConnection == null) return;
activity.unbindService(mConnection);
mClient = null;
mCustomTabsSession = null;
mConnection = null;
}
/**
* Creates or retrieves an exiting CustomTabsSession.
*
* @return a CustomTabsSession.
*/
public CustomTabsSession getSession() {
if (mClient == null) {
mCustomTabsSession = null;
} else if (mCustomTabsSession == null) {
mCustomTabsSession = mClient.newSession(null);
}
return mCustomTabsSession;
}
/**
* Register a Callback to be called when connected or disconnected from the Custom Tabs Service.
*
* @param connectionCallback
*/
public void setConnectionCallback(ConnectionCallback connectionCallback) {
this.mConnectionCallback = connectionCallback;
}
/**
* Binds the Activity to the Custom Tabs Service.
*
* @param activity the activity to be binded to the service.
*/
public void bindCustomTabsService(Activity activity) {
if (mClient != null) return;
String packageName = CustomTabsHelper.getPackageNameToUse(activity);
if (packageName == null) return;
mConnection = new ServiceConnection(this);
CustomTabsClient.bindCustomTabsService(activity, packageName, mConnection);
}
/**
* @return true if call to mayLaunchUrl was accepted.
* @see {@link CustomTabsSession#mayLaunchUrl(Uri, Bundle, List)}.
*/
public boolean mayLaunchUrl(Uri uri, Bundle extras, List<Bundle> otherLikelyBundles) {
if (mClient == null) return false;
CustomTabsSession session = getSession();
return session != null && session.mayLaunchUrl(uri, extras, otherLikelyBundles);
}
@Override
public void onServiceConnected(CustomTabsClient client) {
mClient = client;
mClient.warmup(0L);
if (mConnectionCallback != null) mConnectionCallback.onCustomTabsConnected();
}
@Override
public void onServiceDisconnected() {
mClient = null;
mCustomTabsSession = null;
if (mConnectionCallback != null) mConnectionCallback.onCustomTabsDisconnected();
}
/**
* A Callback for when the service is connected or disconnected. Use those callbacks to
* handle UI changes when the service is connected or disconnected.
*/
public interface ConnectionCallback {
/**
* Called when the service is connected.
*/
void onCustomTabsConnected();
/**
* Called when the service is disconnected.
*/
void onCustomTabsDisconnected();
}
/**
* To be used as a fallback to open the Uri when Custom Tabs is not available.
*/
public interface CustomTabFallback {
/**
* @param activity The Activity that wants to open the Uri.
* @param uri The uri to be opened by the fallback.
*/
void openUri(Activity activity, Uri uri);
}
}

View File

@ -1,130 +0,0 @@
package bou.amine.apps.readerforselfossv2.android.utils.customtabs;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import androidx.browser.customtabs.CustomTabsService;
import android.text.TextUtils;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
import bou.amine.apps.readerforselfossv2.android.utils.customtabs.helpers.KeepAliveService;
@SuppressWarnings("ALL")
class CustomTabsHelper {
private static final String TAG = "CustomTabsHelper";
private static final String STABLE_PACKAGE = "com.android.chrome";
private static final String BETA_PACKAGE = "com.chrome.beta";
private static final String DEV_PACKAGE = "com.chrome.dev";
private static final String LOCAL_PACKAGE = "com.google.android.apps.chrome";
private static final String EXTRA_CUSTOM_TABS_KEEP_ALIVE =
"android.support.customtabs.extra.KEEP_ALIVE";
private static String sPackageNameToUse;
private CustomTabsHelper() {
}
public static void addKeepAliveExtra(Context context, Intent intent) {
Intent keepAliveIntent = new Intent().setClassName(
context.getPackageName(), KeepAliveService.class.getCanonicalName());
intent.putExtra(EXTRA_CUSTOM_TABS_KEEP_ALIVE, keepAliveIntent);
}
/**
* Goes through all apps that handle VIEW intents and have a warmup service. Picks
* the one chosen by the user if there is one, otherwise makes a best effort to return a
* valid package name.
* <p>
* This is <strong>not</strong> threadsafe.
*
* @param context {@link Context} to use for accessing {@link PackageManager}.
* @return The package name recommended to use for connecting to custom tabs related components.
*/
public static String getPackageNameToUse(Context context) {
if (sPackageNameToUse != null) return sPackageNameToUse;
PackageManager pm = context.getPackageManager();
// Get default VIEW intent handler.
Intent activityIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.example.com"));
ResolveInfo defaultViewHandlerInfo = pm.resolveActivity(activityIntent, 0);
String defaultViewHandlerPackageName = null;
if (defaultViewHandlerInfo != null) {
defaultViewHandlerPackageName = defaultViewHandlerInfo.activityInfo.packageName;
}
// Get all apps that can handle VIEW intents.
List<ResolveInfo> resolvedActivityList = pm.queryIntentActivities(activityIntent, 0);
List<String> packagesSupportingCustomTabs = new ArrayList<>();
for (ResolveInfo info : resolvedActivityList) {
Intent serviceIntent = new Intent();
serviceIntent.setAction(CustomTabsService.ACTION_CUSTOM_TABS_CONNECTION);
serviceIntent.setPackage(info.activityInfo.packageName);
if (pm.resolveService(serviceIntent, 0) != null) {
packagesSupportingCustomTabs.add(info.activityInfo.packageName);
}
}
// Now packagesSupportingCustomTabs contains all apps that can handle both VIEW intents
// and service calls.
if (packagesSupportingCustomTabs.isEmpty()) {
sPackageNameToUse = null;
} else if (packagesSupportingCustomTabs.size() == 1) {
sPackageNameToUse = packagesSupportingCustomTabs.get(0);
} else if (!TextUtils.isEmpty(defaultViewHandlerPackageName)
&& !hasSpecializedHandlerIntents(context, activityIntent)
&& packagesSupportingCustomTabs.contains(defaultViewHandlerPackageName)) {
sPackageNameToUse = defaultViewHandlerPackageName;
} else if (packagesSupportingCustomTabs.contains(STABLE_PACKAGE)) {
sPackageNameToUse = STABLE_PACKAGE;
} else if (packagesSupportingCustomTabs.contains(BETA_PACKAGE)) {
sPackageNameToUse = BETA_PACKAGE;
} else if (packagesSupportingCustomTabs.contains(DEV_PACKAGE)) {
sPackageNameToUse = DEV_PACKAGE;
} else if (packagesSupportingCustomTabs.contains(LOCAL_PACKAGE)) {
sPackageNameToUse = LOCAL_PACKAGE;
}
return sPackageNameToUse;
}
/**
* Used to check whether there is a specialized handler for a given intent.
*
* @param intent The intent to check with.
* @return Whether there is a specialized handler for the given intent.
*/
private static boolean hasSpecializedHandlerIntents(Context context, Intent intent) {
try {
PackageManager pm = context.getPackageManager();
List<ResolveInfo> handlers = pm.queryIntentActivities(
intent,
PackageManager.GET_RESOLVED_FILTER);
if (handlers == null || handlers.isEmpty()) {
return false;
}
for (ResolveInfo resolveInfo : handlers) {
IntentFilter filter = resolveInfo.filter;
if (filter == null) continue;
if (filter.countDataAuthorities() == 0 || filter.countDataPaths() == 0) continue;
if (resolveInfo.activityInfo == null) continue;
return true;
}
} catch (RuntimeException e) {
Log.e(TAG, "Runtime exception while getting specialized handlers");
}
return false;
}
/**
* @return All possible chrome package names that provide custom tabs feature.
*/
public static String[] getPackages() {
return new String[]{"", STABLE_PACKAGE, BETA_PACKAGE, DEV_PACKAGE, LOCAL_PACKAGE};
}
}

View File

@ -1,33 +0,0 @@
package bou.amine.apps.readerforselfossv2.android.utils.customtabs;
import android.content.ComponentName;
import androidx.browser.customtabs.CustomTabsClient;
import androidx.browser.customtabs.CustomTabsServiceConnection;
import java.lang.ref.WeakReference;
/**
* Implementation for the CustomTabsServiceConnection that avoids leaking the
* ServiceConnectionCallback
*/
public class ServiceConnection extends CustomTabsServiceConnection {
// A weak reference to the ServiceConnectionCallback to avoid leaking it.
private WeakReference<ServiceConnectionCallback> mConnectionCallback;
public ServiceConnection(ServiceConnectionCallback connectionCallback) {
mConnectionCallback = new WeakReference<>(connectionCallback);
}
@Override
public void onCustomTabsServiceConnected(ComponentName name, CustomTabsClient client) {
ServiceConnectionCallback connectionCallback = mConnectionCallback.get();
if (connectionCallback != null) connectionCallback.onServiceConnected(client);
}
@Override
public void onServiceDisconnected(ComponentName name) {
ServiceConnectionCallback connectionCallback = mConnectionCallback.get();
if (connectionCallback != null) connectionCallback.onServiceDisconnected();
}
}

View File

@ -1,19 +0,0 @@
package bou.amine.apps.readerforselfossv2.android.utils.customtabs;
import androidx.browser.customtabs.CustomTabsClient;
public interface ServiceConnectionCallback {
/**
* Called when the service is connected.
*
* @param client a CustomTabsClient
*/
void onServiceConnected(CustomTabsClient client);
/**
* Called when the service is disconnected.
*/
void onServiceDisconnected();
}

View File

@ -1,15 +0,0 @@
package bou.amine.apps.readerforselfossv2.android.utils.customtabs.helpers;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
public class KeepAliveService extends Service {
private static final Binder sBinder = new Binder();
@Override
public IBinder onBind(Intent intent) {
return sBinder;
}
}

View File

@ -1,11 +1,10 @@
/* 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 androidx.recyclerview.widget.RecyclerView
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) {

View File

@ -2,33 +2,26 @@ package bou.amine.apps.readerforselfossv2.android.utils.glide
import android.content.Context
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.util.Base64
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory
import android.widget.ImageView
import bou.amine.apps.readerforselfossv2.android.utils.Config
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory
import com.bumptech.glide.Glide
import com.bumptech.glide.RequestBuilder
import com.bumptech.glide.RequestManager
import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.load.model.LazyHeaders
import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.BitmapImageViewTarget
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.InputStream
fun Context.bitmapCenterCrop(config: Config, url: String, iv: ImageView) =
fun Context.bitmapCenterCrop(url: String, iv: ImageView) =
Glide.with(this)
.asBitmap()
.loadMaybeBasicAuth(config, url)
.load(url)
.apply(RequestOptions.centerCropTransform())
.into(iv)
fun Context.circularBitmapDrawable(config: Config, url: String, iv: ImageView) =
fun Context.circularBitmapDrawable(url: String, iv: ImageView) =
Glide.with(this)
.asBitmap()
.loadMaybeBasicAuth(config, url)
.load(url)
.apply(RequestOptions.centerCropTransform())
.into(object : BitmapImageViewTarget(iv) {
override fun setResource(resource: Bitmap?) {
@ -41,26 +34,6 @@ fun Context.circularBitmapDrawable(config: Config, url: String, iv: ImageView) =
}
})
fun RequestBuilder<Bitmap>.loadMaybeBasicAuth(config: Config, url: String): RequestBuilder<Bitmap> {
val builder: LazyHeaders.Builder = LazyHeaders.Builder()
if (config.httpUserLogin.isNotEmpty() || config.httpUserPassword.isNotEmpty()) {
val basicAuth = "Basic " + Base64.encodeToString("${config.httpUserLogin}:${config.httpUserPassword}".toByteArray(), Base64.NO_WRAP)
builder.addHeader("Authorization", basicAuth)
}
val glideUrl = GlideUrl(url, builder.build())
return this.load(glideUrl)
}
fun RequestManager.loadMaybeBasicAuth(config: Config, url: String): RequestBuilder<Drawable> {
val builder: LazyHeaders.Builder = LazyHeaders.Builder()
if (config.httpUserLogin.isNotEmpty() || config.httpUserPassword.isNotEmpty()) {
val basicAuth = "Basic " + Base64.encodeToString("${config.httpUserLogin}:${config.httpUserPassword}".toByteArray(), Base64.NO_WRAP)
builder.addHeader("Authorization", basicAuth)
}
val glideUrl = GlideUrl(url, builder.build())
return this.load(glideUrl)
}
fun getBitmapInputStream(bitmap:Bitmap,compressFormat: Bitmap.CompressFormat): InputStream {
val byteArrayOutputStream = ByteArrayOutputStream()
bitmap.compress(compressFormat, 80, byteArrayOutputStream)

View File

@ -1,33 +0,0 @@
package bou.amine.apps.readerforselfossv2.android.utils.glide
import android.content.Context
import bou.amine.apps.readerforselfossv2.android.utils.getUnsafeHttpClient
import com.bumptech.glide.Glide
import com.bumptech.glide.GlideBuilder
import com.bumptech.glide.Registry
import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.module.GlideModule
import com.russhwolf.settings.Settings
import java.io.InputStream
class SelfSignedGlideModule : GlideModule {
override fun applyOptions(context: Context?, builder: GlideBuilder?) {
}
override fun registerComponents(context: Context?, glide: Glide?, registry: Registry?) {
if (context != null) {
val settings = Settings()
if (settings.getBoolean("isSelfSignedCert", false)) {
val client = getUnsafeHttpClient().build()
registry?.append(
GlideUrl::class.java,
InputStream::class.java,
com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader.Factory(client)
)
}
}
}
}

View File

@ -1,52 +1,14 @@
package bou.amine.apps.readerforselfossv2.android.utils.network
import android.content.Context
import android.graphics.Color
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.os.Build
import android.view.View
import android.widget.TextView
import bou.amine.apps.readerforselfossv2.android.R
import com.google.android.material.snackbar.Snackbar
var snackBarShown = false
var view: View? = null
lateinit var s: Snackbar
fun Context.isNetworkAvailable(
v: View? = null,
overrideOffline: Boolean = false
): Boolean {
val networkIsAccessible = isNetworkAccessible(this)
if (v != null && (!networkIsAccessible || overrideOffline) && (!snackBarShown || v != view)) {
view = v
s = Snackbar
.make(
v,
R.string.no_network_connectivity,
Snackbar.LENGTH_INDEFINITE
)
s.setAction(android.R.string.ok) {
snackBarShown = false
s.dismiss()
}
val view = s.view
val tv: TextView = view.findViewById(com.google.android.material.R.id.snackbar_text)
tv.setTextColor(Color.WHITE)
s.show()
snackBarShown = true
}
if (snackBarShown && networkIsAccessible && !overrideOffline) {
s.dismiss()
}
return if(overrideOffline) overrideOffline else networkIsAccessible
}
private fun isNetworkAccessible(context: Context): Boolean {
fun isNetworkAccessible(context: Context): Boolean {
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

View File

@ -1,72 +0,0 @@
package bou.amine.apps.readerforselfossv2.android.utils.persistence
import bou.amine.apps.readerforselfossv2.android.model.getSourceTitle
import bou.amine.apps.readerforselfossv2.android.model.getTitleDecoded
import bou.amine.apps.readerforselfossv2.android.persistence.entities.AndroidItemEntity
import bou.amine.apps.readerforselfossv2.android.persistence.entities.SourceEntity
import bou.amine.apps.readerforselfossv2.android.persistence.entities.TagEntity
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
fun TagEntity.toView(): SelfossModel.Tag =
SelfossModel.Tag(
this.tag,
this.color,
this.unread
)
fun SourceEntity.toView(): SelfossModel.Source =
SelfossModel.Source(
this.id.toInt(),
this.title,
this.tags.split(","),
this.spout,
this.error,
this.icon
)
fun SelfossModel.Source.toEntity(): SourceEntity =
SourceEntity(
this.id.toString(),
this.getTitleDecoded(),
this.tags.joinToString(","),
this.spout,
this.error,
this.icon.orEmpty()
)
fun SelfossModel.Tag.toEntity(): TagEntity =
TagEntity(
this.tag,
this.color,
this.unread
)
fun AndroidItemEntity.toView(): SelfossModel.Item =
SelfossModel.Item(
this.id.toInt(),
this.datetime,
this.title,
this.content,
this.unread,
this.starred,
this.thumbnail,
this.icon,
this.link,
this.sourcetitle,
this.tags.split(",")
)
fun SelfossModel.Item.toEntity(): AndroidItemEntity =
AndroidItemEntity(
this.id.toString(),
this.datetime,
this.getTitleDecoded(),
this.content,
this.unread,
this.starred,
this.thumbnail,
this.icon,
this.link,
this.getSourceTitle(),
this.tags.joinToString(",")
)

View File

@ -0,0 +1,30 @@
package bou.amine.apps.readerforselfossv2.android.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import bou.amine.apps.readerforselfossv2.repository.Repository
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.launch
class AppViewModel(private val repository: Repository) : ViewModel() {
private val _networkAvailableProvider = MutableSharedFlow<Boolean>()
val networkAvailableProvider = _networkAvailableProvider.asSharedFlow()
private var wasConnected = true
init {
viewModelScope.launch {
repository.isConnectionAvailable.collect { isConnected ->
if (repository.connectionMonitored) {
if (isConnected && !wasConnected && repository.connectionMonitored) {
_networkAvailableProvider.emit(true)
wasConnected = true
} else if (!isConnected && wasConnected && repository.connectionMonitored){
_networkAvailableProvider.emit(false)
wasConnected = false
}
}
}
}
}
}

View File

@ -1,8 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:drawable="@color/ic_launcher_background"/>
<item>
<shape android:shape="rectangle" >
<solid android:color="?attr/colorSurface" />
</shape>
</item>
<item>
<bitmap

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

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

@ -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="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,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="#FFFFFFFF"
android:pathData="M13,12h7v1.5h-7zM13,9.5h7L20,11h-7zM13,14.5h7L20,16h-7zM21,4L3,4c-1.1,0 -2,0.9 -2,2v13c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,6c0,-1.1 -0.9,-2 -2,-2zM21,19h-9L12,6h9v13z"/>
</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="M12,3c-4.97,0 -9,4.03 -9,9s4.03,9 9,9c0.83,0 1.5,-0.67 1.5,-1.5 0,-0.39 -0.15,-0.74 -0.39,-1.01 -0.23,-0.26 -0.38,-0.61 -0.38,-0.99 0,-0.83 0.67,-1.5 1.5,-1.5L16,16c2.76,0 5,-2.24 5,-5 0,-4.42 -4.03,-8 -9,-8zM6.5,12c-0.83,0 -1.5,-0.67 -1.5,-1.5S5.67,9 6.5,9 8,9.67 8,10.5 7.33,12 6.5,12zM9.5,8C8.67,8 8,7.33 8,6.5S8.67,5 9.5,5s1.5,0.67 1.5,1.5S10.33,8 9.5,8zM14.5,8c-0.83,0 -1.5,-0.67 -1.5,-1.5S13.67,5 14.5,5s1.5,0.67 1.5,1.5S15.33,8 14.5,8zM17.5,12c-0.83,0 -1.5,-0.67 -1.5,-1.5S16.67,9 17.5,9s1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5z"/>
</vector>

View File

@ -0,0 +1,7 @@
<?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="name=Source Code Pro&amp;weight=500"
app:fontProviderCerts="@array/com_google_android_gms_fonts_certs">
</font-family>

View File

@ -14,12 +14,12 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
<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"
app:theme="@style/ToolBarStyle"
app:popupTheme="?attr/toolbarPopupTheme" />
/>
</com.google.android.material.appbar.AppBarLayout>
@ -107,8 +107,7 @@
app:layout_constraintTop_toBottomOf="@+id/tags"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:layout_height="40dp"
android:theme="@style/App.Spinner"/>
android:layout_height="40dp"/>
<Button
android:text="@string/add_source_save"
@ -116,7 +115,7 @@
android:layout_height="wrap_content"
android:id="@+id/saveBtn"
android:elevation="5dp"
android:textColor="?attr/colorAccent"
android:textColor="?android:textColorPrimary"
android:layout_marginEnd="16dp"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginRight="16dp"

View File

@ -28,12 +28,12 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
<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"
app:theme="@style/ToolBarStyle"
app:popupTheme="?attr/toolbarPopupTheme" />
/>
</com.google.android.material.appbar.AppBarLayout>
@ -78,10 +78,12 @@
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.ashokvarma.bottomnavigation.BottomNavigationBar
android:layout_gravity="bottom"
android:id="@+id/bottomBar"
android:layout_width="match_parent"
android:layout_height="60dp"/>
android:layout_height="60dp"
android:layout_gravity="bottom"
app:bnbActiveColor="@color/colorAccent"
app:bnbBackgroundColor="?attr/bottomBarBackground" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.mikepenz.materialdrawer.widget.MaterialDrawerSliderView

View File

@ -16,8 +16,8 @@
android:id="@+id/toolBar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:popupTheme="?attr/toolbarPopupTheme"
app:theme="@style/ToolBarStyle" />
/>
</com.google.android.material.appbar.AppBarLayout>

View File

@ -10,12 +10,12 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
<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"
app:theme="@style/ToolBarStyle"
app:popupTheme="?attr/toolbarPopupTheme" />
/>
</com.google.android.material.appbar.AppBarLayout>
<LinearLayout
@ -82,45 +82,6 @@
android:maxLines="1"
android:visibility="gone" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/withHttpLogin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/withHttpLoginSwitch" />
<EditText
android:id="@+id/httpLoginView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autofillHints="username"
android:hint="@string/prompt_http_login"
android:inputType="text"
android:visibility="gone" />
<EditText
android:id="@+id/httpPasswordView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autofillHints="password"
android:hint="@string/prompt_http_password"
android:inputType="textPassword"
android:visibility="gone" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/withSelfhostedCert"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/self_hosted_cert_switch" />
<TextView
android:id="@+id/warningText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/self_signed_cert_warning"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:visibility="gone" />
<Button
android:id="@+id/signInButton"
style="?android:textAppearanceSmall"

View File

@ -17,8 +17,10 @@
android:id="@+id/toolBar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:theme="@style/ToolBarStyle"
app:popupTheme="?attr/toolbarPopupTheme"
app:theme="@style/ToolBarStyle" />
/>
</com.google.android.material.appbar.AppBarLayout>

View File

@ -1,4 +1,5 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -8,11 +9,11 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
<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:theme="@style/ToolBarStyle" />
/>
</com.google.android.material.appbar.AppBarLayout>
<FrameLayout

View File

@ -10,12 +10,12 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
<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"
app:theme="@style/ToolBarStyle"
app:popupTheme="?attr/toolbarPopupTheme" />
/>
</com.google.android.material.appbar.AppBarLayout>

View File

@ -16,8 +16,7 @@
app:layout_constraintTop_toTopOf="parent"
card_view:cardElevation="2dp"
card_view:cardUseCompatPadding="true"
card_view:layout_constraintBottom_toBottomOf="parent"
app:cardBackgroundColor="?cardBackgroundColor">
card_view:layout_constraintBottom_toBottomOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"

View File

@ -66,7 +66,7 @@
android:layout_marginRight="16dp"
android:layout_marginTop="24dp"
android:paddingBottom="48dp"
android:background="?android:colorBackground"
android:background="?attr/webviewBackground"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"

View File

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

View File

@ -8,12 +8,6 @@
android:title="@string/unmark"
app:showAsAction="ifRoom" />
<item
android:id="@+id/more_action"
android:icon="@drawable/ic_chrome_reader_mode_white_24dp"
android:title="@string/reader_action_more"
app:showAsAction="ifRoom" />
<item
android:id="@+id/open_action"
android:icon="@drawable/ic_open_in_browser_white_24dp"

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/clear"
android:icon="@drawable/ic_history_white_24dp"
android:title="@string/drawer_action_clear"
app:showAsAction="ifRoom" />
</menu>

View File

@ -3,16 +3,13 @@
<string name="app_name">"Lector per a Selfoss"</string>
<string name="title_activity_login">"Inicia la sessió"</string>
<string name="prompt_password">"Contrasenya"</string>
<string name="prompt_http_password">"Contrasenya HTTP"</string>
<string name="action_sign_in">"Vés-hi"</string>
<string name="error_invalid_password">"La contrasenya és massa curta"</string>
<string name="error_field_required">"Camp necessari"</string>
<string name="prompt_url">"URL"</string>
<string name="withLoginSwitch">"Autenticació (si és necessària)"</string>
<string name="withHttpLoginSwitch">"Autenticació HTTP (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="prompt_http_login">"Nom d'usuari HTTP"</string>
<string name="label_share">"Comparteix"</string>
<string name="readAll">"Llegeix-ho tot"</string>
<string name="action_disconnect">"Desconnecta't"</string>
@ -26,9 +23,6 @@
<string name="wrong_infos">"Torneu a comprovar la informació."</string>
<string name="all_posts_not_read">"No s'han llegit totes les publicacions"</string>
<string name="all_posts_read">"S'han llegit totes les publicacions"</string>
<string name="cant_get_favs">"No es poden obtenir preferits"</string>
<string name="cant_get_new_elements">"No es pot accedir als articles nous"</string>
<string name="cant_get_read">"No es poden llegir els articles"</string>
<string name="nothing_here">"No hi ha res"</string>
<string name="tab_new">"Nou"</string>
<string name="tab_read">"Tot"</string>
@ -40,36 +34,22 @@
<string name="addStringNoUrl">"Inicieu la sessió per afegir fonts."</string>
<string name="cant_get_sources">"No es pot obtenir la llista de fonts."</string>
<string name="cant_create_source">"No es pot crear la font."</string>
<string name="cant_get_spouts">"No es pot obtenir la llista de canals."</string>
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
<string name="cant_get_spouts">"Can't get spouts list. There may ben an api issue."</string>
<string name="form_not_complete">"El formulari no està complet"</string>
<string name="pref_header_links">"Enllaços"</string>
<string name="issue_tracker_link">"Detector de problemes"</string>
<string name="issue_tracker_summary">"Informa d'un error o pregunta sobre funcions noves"</string>
<string name="warning_wrong_url">"ADVERTÈNCIA"</string>
<string name="pref_switch_card_view_title">"Visualització de targeta"</string>
<string name="cant_mark_favortie">"No es pot marcar l'article com a preferit"</string>
<string name="cant_unmark_favortie">"No es pot treure l'element de preferits"</string>
<string name="share">"Comparteix"</string>
<string name="rating_prompt_title">"Us agrada l'aplicació?"</string>
<string name="rating_prompt_yes">"Sí."</string>
<string name="rating_prompt_no">"No gaire…"</string>
<string name="rating_prompt_feedback_title">"Ens podeu dir per què?"</string>
<string name="rating_prompt_feedback_yes">"D'acord."</string>
<string name="rating_prompt_feedback_no">"Ara no."</string>
<string name="rating_prompt_rating_title">"Perfecte! Ens podeu puntuar a la Botiga?"</string>
<string name="rating_prompt_rating_yes">"Sí."</string>
<string name="rating_prompt_rating_no">"Ara no."</string>
<string name="rating_prompt_thanks">"Gràcies. La vostra opinió ens ajuda a millorar l'aplicació."</string>
<string name="switch_unread_count">"Mostra el recompte d'articles no llegits amb un distintiu a la barra inferior."</string>
<string name="switch_unread_count_title">"Recompte d'articles no llegits"</string>
<string name="display_all_counts_title">"Recompte d'articles llegits i preferits"</string>
<string name="text_wrong_url">"Sembla que esteu utilitzant un URL no vàlid. Assegureu-vos que és correcte, i si el problema persisteix, poseu-vos en contacte amb mi (a través de l'enllaç de contacte que hi ha a la Botiga). Tingueu en compte que per utilitzar aquesta aplicació cal que també utilitzeu Selfoss. Si no, no podreu accedir a canals RSS."</string>
<string name="pref_general_internal_browser_title">"Obre els enllaços dins de l'aplicació"</string>
<string name="pref_general_internal_browser_on">"Els articles s'obriran dins de l'aplicació"</string>
<string name="pref_general_internal_browser_off">"Els articles s'obriran amb el navegador predeterminat"</string>
<string name="prefer_article_viewer_title">"Obre el visualitzador d'articles"</string>
<string name="prefer_article_viewer_on">"S'obrirà el visualitzador d'articles en lloc del navegador intern"</string>
<string name="prefer_article_viewer_off">"S'obrirà el navegador intern en lloc del visualitzador d'articles"</string>
<string name="pref_article_viewer_title">"Obre els enllaços dins de l'aplicació"</string>
<string name="pref_article_viewer_on">"Els articles s'obriran dins de l'aplicació"</string>
<string name="pref_article_viewer_off">"Els articles s'obriran amb el navegador predeterminat"</string>
<string name="pref_general_category_links">"Gestió d'enllaços"</string>
<string name="pref_general_category_displaying">"Visualització"</string>
<string name="pref_switch_card_view_on">"Els articles es mostraran com a targetes"</string>
@ -82,17 +62,12 @@
<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="cant_mark_read">No es pot marcar l\'article com a llegit</string>
<string name="cant_mark_unread">Can\'t mark article as unread</string>
<string name="drawer_error_loading_tags">S\'ha produït un error en carregar les etiquetes</string>
<string name="drawer_error_loading_sources">S\'ha produït un error en carregar les fonts</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="no_tags_loaded">No s\'ha carregat cap etiqueta</string>
<string name="no_sources_loaded">No s\'ha carregat cap font</string>
<string name="drawer_loading">S\'està carregant…</string>
<string name="menu_home_search">Cerca</string>
<string name="can_delete_source">No es pot suprimir la font</string>
@ -100,16 +75,9 @@
<string name="pref_header_theme">Temes</string>
<string name="default_theme">Predeterminat</string>
<string name="default_dark_theme">Predeterminat/Fosc</string>
<string name="pref_header_debug">Depuració</string>
<string name="self_hosted_cert_switch">Utilitzeu un certificat autoallotjat?</string>
<string name="self_signed_cert_warning">Per raons de seguretat, els certificats autosignats no seran compatibles per defecte. En activar aquesta opció, sereu responsable de qualsevol problema de seguretat que es pugui produir.</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="summary_debug_identifier">Identificador de depuració</string>
<string name="unique_id_to_clipboard">S\'ha copiat l\'identificador al porta-retalls</string>
<string name="display_header_drawer_summary">Mostra una capçalera amb la instància URL de Selfoss al panell lateral.</string>
<string name="display_header_drawer_title">Capçalera de menú</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>
@ -120,19 +88,12 @@
<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>
<string name="add_to_favs_reader">Afegeix als preferits</string>
<string name="remove_to_favs_reader">Suprimeix dels preferits</string>
<string name="pref_content_reader_font_size">Mida de la lletra del lector darticles</string>
<string name="pref_header_viewer">Visualitzador d\'articles</string>
<string name="refresh_dialog_message">Aquesta acció actualitzarà la vostra instància de Selfoss.</string>
<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="pref_acra_alwaysaccept">Envia informes d\'error automàtics</string>
<string name="pref_acra_alwaysaccept_enabled">S\'enviaran informes d\'error automàticament</string>
<string name="pref_acra_alwaysaccept_disabled">Us preguntarem abans d\'enviar un informe d\'error.</string>
<string name="pref_debug_crash_reports">Informes d\'error</string>
<string name="pref_debug_debug_logs">Registre de depuració (s\'enviarà automàticament)</string>
<string name="acra_login">Habilita el registre</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>
@ -141,7 +102,8 @@
<string name="pref_switch_items_caching">Guarda els elements per utilitzar-los sense connexió</string>
<string name="pref_switch_update_sources">Check for new sources and tags</string>
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
<string name="no_network_connectivity">Sense connexió!</string>
<string name="network_connectivity_lost">"Network connection lost"</string>
<string name="network_connectivity_retrieved">"Network connection is now available"</string>
<string name="pref_switch_periodic_refresh">Sincronitza els articles</string>
<string name="pref_switch_periodic_refresh_off">Els articles no se sincronitzaran en segon pla</string>
<string name="pref_switch_periodic_refresh_on">Els articles se sincronitzaran periòdicament</string>
@ -166,4 +128,9 @@
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string>
<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="drawer_error_loading_sources">Error loading sources…</string>
</resources>

View File

@ -3,16 +3,13 @@
<string name="app_name">"Reader für selfoss"</string>
<string name="title_activity_login">"Anmelden"</string>
<string name="prompt_password">"Passwort"</string>
<string name="prompt_http_password">"HTTP Passwort"</string>
<string name="action_sign_in">"Fortfahren"</string>
<string name="error_invalid_password">"Passwort ist nicht lang genug"</string>
<string name="error_field_required">"Pflichtfeld"</string>
<string name="prompt_url">"URL"</string>
<string name="withLoginSwitch">"Anmeldung erforderlich?"</string>
<string name="withHttpLoginSwitch">"HTTP 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="prompt_http_login">"HTTP Benutzername"</string>
<string name="label_share">"Teilen"</string>
<string name="readAll">"Alle gelesen"</string>
<string name="action_disconnect">"Verbindung trennen"</string>
@ -26,9 +23,6 @@
<string name="wrong_infos">"Überprüfe deine Angaben noch einmal."</string>
<string name="all_posts_not_read">"Nicht alle Beiträge wurden gelesen"</string>
<string name="all_posts_read">"Alle Beiträge wurden gelesen"</string>
<string name="cant_get_favs">"Favoriten können nicht abgerufen werden"</string>
<string name="cant_get_new_elements">"Neue Artikel können nicht abgerufen werden"</string>
<string name="cant_get_read">"Gelese Artikel können nicht abgerufen werden"</string>
<string name="nothing_here">"Keine Einträge vorhanden"</string>
<string name="tab_new">"Neu"</string>
<string name="tab_read">"Alle"</string>
@ -40,36 +34,22 @@
<string name="addStringNoUrl">"Melde dich an um Quellen hinzuzufügen."</string>
<string name="cant_get_sources">"Quellen können nicht abgerufen werden."</string>
<string name="cant_create_source">"Quelle kann nicht gespeichert werden."</string>
<string name="cant_get_spouts">"Can't get spouts list."</string>
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
<string name="cant_get_spouts">"Can't get spouts list. There may ben an api issue."</string>
<string name="form_not_complete">"Das Formular ist nicht vollständig"</string>
<string name="pref_header_links">"Links"</string>
<string name="issue_tracker_link">"Issue Tracker"</string>
<string name="issue_tracker_summary">"Melde einen Bug oder rege ein neues Feature an"</string>
<string name="warning_wrong_url">"WARNUNG"</string>
<string name="pref_switch_card_view_title">"Kachelansicht"</string>
<string name="cant_mark_favortie">"Artikel kann nicht als Favorit markiert werden"</string>
<string name="cant_unmark_favortie">"Eintrag kann nicht aus Favoriten entfernt werden"</string>
<string name="share">"Teilen"</string>
<string name="rating_prompt_title">"Gefällt Dir die App?"</string>
<string name="rating_prompt_yes">"Ja!"</string>
<string name="rating_prompt_no">"Nicht wirklich…"</string>
<string name="rating_prompt_feedback_title">"Magst du uns sagen warum?"</string>
<string name="rating_prompt_feedback_yes">"OK!"</string>
<string name="rating_prompt_feedback_no">"Nicht jetzt."</string>
<string name="rating_prompt_rating_title">"Wunderbar! Magst du uns im Play Store bewerten?"</string>
<string name="rating_prompt_rating_yes">"Sicher!"</string>
<string name="rating_prompt_rating_no">"Nicht jetzt."</string>
<string name="rating_prompt_thanks">"Vielen Dank, dein Feedback hilft die App zu verbessern!"</string>
<string name="switch_unread_count">"Zeige die Zahl ungelesener Artikel in der unteren Leiste."</string>
<string name="switch_unread_count_title">"Zeige Anzahl ungelesener Artikel"</string>
<string name="display_all_counts_title">"Zeige Anzahl der Favoriten und gelesenen Artikel"</string>
<string name="text_wrong_url">"Sie scheinen eine ungültige URL verwenden. Stellen Sie sicher, dass die URL richtig ist. Sollte das Problem weiterhin bestehen kontaktieren Sie mich (über den Playstore-Kontakt-Link). Bitte beachten Sie, dass Sie Selfoss benötigen um RSS-Feeds zu lesen."</string>
<string name="pref_general_internal_browser_title">"Öffne Links innerhalb der App"</string>
<string name="pref_general_internal_browser_on">"Artikel werden innerhalb der App geöffnet"</string>
<string name="pref_general_internal_browser_off">"Artikel werden mit deinem Standard-Browser geöffnet"</string>
<string name="prefer_article_viewer_title">"Verwenden Sie den Artikel-viewer"</string>
<string name="prefer_article_viewer_on">"Artikel-Viewer wird anstelle des internen Browser verwendet"</string>
<string name="prefer_article_viewer_off">"Der internen Browser wird anstelle des Artikel-Viewer verwendet"</string>
<string name="pref_article_viewer_title">"Öffne Links innerhalb der App"</string>
<string name="pref_article_viewer_on">"Artikel werden innerhalb der App geöffnet"</string>
<string name="pref_article_viewer_off">"Artikel werden mit deinem Standard-Browser geöffnet"</string>
<string name="pref_general_category_links">"Umgang mit Links"</string>
<string name="pref_general_category_displaying">"Ansicht"</string>
<string name="pref_switch_card_view_on">"Artikel werden als Kacheln angezeigt"</string>
@ -82,17 +62,12 @@
<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="cant_mark_read">Artikel kann nicht als gelesen markiert werden</string>
<string name="cant_mark_unread">Can\'t mark article as unread</string>
<string name="drawer_error_loading_tags">Fehler beim Laden der Tags…</string>
<string name="drawer_error_loading_sources">Fehler beim Laden der Quellen…</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="no_tags_loaded">No tags loaded</string>
<string name="no_sources_loaded">Keine Quellen geladen</string>
<string name="drawer_loading">Lade…</string>
<string name="menu_home_search">Suche</string>
<string name="can_delete_source">Can\'t delete the source…</string>
@ -100,16 +75,9 @@
<string name="pref_header_theme">Designs</string>
<string name="default_theme">Standard</string>
<string name="default_dark_theme">Standard (Dunkel)</string>
<string name="pref_header_debug">Debug</string>
<string name="self_hosted_cert_switch">Verwenden Sie einen selbst gehostetes Zertifikat?</string>
<string name="self_signed_cert_warning">Due to security reasons, self signed certificates are not supported by default. By activating this, I\'ll not be responsible of any security problem you encounter.</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="summary_debug_identifier">Debug identifier</string>
<string name="unique_id_to_clipboard">Identifier copied to your clipboard</string>
<string name="display_header_drawer_summary">Display a header with the selfoss instance url on the lateral drawer.</string>
<string name="display_header_drawer_title">Account header</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>
@ -120,19 +88,12 @@
<string name="reader_action_share">Teilen</string>
<string name="pref_switch_actions_pager_scroll_on">Mark articles as read when swiping between articles.</string>
<string name="add_to_favs_reader">Zu Favoriten hinzufügen</string>
<string name="remove_to_favs_reader">Aus Favoriten entfernen</string>
<string name="pref_content_reader_font_size">Article reader content font size</string>
<string name="pref_header_viewer">Article viewer</string>
<string name="refresh_dialog_message">This will refresh your Selfoss instance.</string>
<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="pref_acra_alwaysaccept">Automatically send crash reports</string>
<string name="pref_acra_alwaysaccept_enabled">Fehlerberichte werden automatisch gesendet</string>
<string name="pref_acra_alwaysaccept_disabled">Will ask everytime when sending crash reports.</string>
<string name="pref_debug_crash_reports">Fehlerberichte</string>
<string name="pref_debug_debug_logs">Debug logging (these will be sent without a dialog)</string>
<string name="acra_login">Protokollierung aktivieren</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>
@ -141,7 +102,8 @@
<string name="pref_switch_items_caching">Save items for offline use</string>
<string name="pref_switch_update_sources">Check for new sources and tags</string>
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
<string name="no_network_connectivity">Nicht verbunden !</string>
<string name="network_connectivity_lost">"Die Netzwerkverbindung wurde unterbrochen"</string>
<string name="network_connectivity_retrieved">"Netzwerkverbindung ist jetzt verfügbar"</string>
<string name="pref_switch_periodic_refresh">Synchronisiere Artikel</string>
<string name="pref_switch_periodic_refresh_off">Artikel werden nicht im Hintergrund synchronisiert</string>
<string name="pref_switch_periodic_refresh_on">Die Artikel werden regelmäßig synchronisiert</string>
@ -166,4 +128,9 @@
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string>
<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="drawer_error_loading_sources">Error loading sources…</string>
</resources>

View File

@ -3,16 +3,13 @@
<string name="app_name">"Lector para Selfoss"</string>
<string name="title_activity_login">"Iniciar sesión"</string>
<string name="prompt_password">"Contraseña"</string>
<string name="prompt_http_password">"Contraseña HTTP"</string>
<string name="action_sign_in">"Empezar"</string>
<string name="error_invalid_password">"La contraseña no es suficientemente larga"</string>
<string name="error_field_required">"Campo requerido"</string>
<string name="prompt_url">"Url"</string>
<string name="withLoginSwitch">"Inicio de sesión requerido ?"</string>
<string name="withHttpLoginSwitch">"Inicio de sesión HTTP 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="prompt_http_login">"Nombre de usuario HTTP"</string>
<string name="label_share">"Compartir"</string>
<string name="readAll">"Leer todo"</string>
<string name="action_disconnect">"Desconectar"</string>
@ -26,9 +23,6 @@
<string name="wrong_infos">"Revise sus datos de nuevo."</string>
<string name="all_posts_not_read">"No todas las publicaciones fueron leídas"</string>
<string name="all_posts_read">"Todas las publicaciones fueron leídas"</string>
<string name="cant_get_favs">"No se pueden obtener favoritos"</string>
<string name="cant_get_new_elements">"No puede recibir nuevos artículos"</string>
<string name="cant_get_read">"No puede recibir artículos leídos"</string>
<string name="nothing_here">"Nada aquí"</string>
<string name="tab_new">"Nuevo"</string>
<string name="tab_read">"Todo"</string>
@ -40,36 +34,22 @@
<string name="addStringNoUrl">"Iniciar sesión para añadir fuentes."</string>
<string name="cant_get_sources">"No se puede obtener la lista de fuentes."</string>
<string name="cant_create_source">"No se puede crear la fuente."</string>
<string name="cant_get_spouts">"No se puede obtener la lista de fuentes."</string>
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
<string name="cant_get_spouts">"Can't get spouts list. There may ben an api issue."</string>
<string name="form_not_complete">"El formulario no está completo"</string>
<string name="pref_header_links">"Enlaces"</string>
<string name="issue_tracker_link">"Rastreador de Incidencias"</string>
<string name="issue_tracker_summary">"Reportar un error o solicitar una nueva función"</string>
<string name="warning_wrong_url">"ADVERTENCIA"</string>
<string name="pref_switch_card_view_title">"Vista de la tarjeta"</string>
<string name="cant_mark_favortie">"No puede marcar el artículo como favorito"</string>
<string name="cant_unmark_favortie">"No se puede quitar el artículo de favoritos"</string>
<string name="share">"Compartir"</string>
<string name="rating_prompt_title">"¿Disfrutando la aplicación?"</string>
<string name="rating_prompt_yes">"¡Sí!"</string>
<string name="rating_prompt_no">"La verdad es que no…"</string>
<string name="rating_prompt_feedback_title">"¿Puede decirnos por qué?"</string>
<string name="rating_prompt_feedback_yes">"¡Vale!"</string>
<string name="rating_prompt_feedback_no">"Ahora no."</string>
<string name="rating_prompt_rating_title">"¡Excelente! ¿Puede valorarnos en la tienda?"</string>
<string name="rating_prompt_rating_yes">"¡Claro!"</string>
<string name="rating_prompt_rating_no">"No en este momento."</string>
<string name="rating_prompt_thanks">"¡Gracias, sus comentarios ayudan a mejorar la aplicación!"</string>
<string name="switch_unread_count">"Mostrar el recuento no leído como una insignia de la barra inferior."</string>
<string name="switch_unread_count_title">"Mostrar recuento no leído"</string>
<string name="display_all_counts_title">"Mostrar recuento de favoritos y leídos"</string>
<string name="text_wrong_url">"Parece estar tratando de utilizar una dirección URL inválida. Asegúrese de que sea correcta y si el problema persiste, póngase en contacto conmigo (mediante el enlace de contacto de la tienda). Tenga en cuenta que la aplicación necesita utilizar Selfoss. No se puede acceder al contenido RSS sin él."</string>
<string name="pref_general_internal_browser_title">"Abrir enlaces dentro de la aplicación"</string>
<string name="pref_general_internal_browser_on">"Los artículos se abrirán dentro de la aplicación"</string>
<string name="pref_general_internal_browser_off">"Los artículos se abrirán con tu navegador predeterminado"</string>
<string name="prefer_article_viewer_title">"Utilizar el visor de artículo"</string>
<string name="prefer_article_viewer_on">"Se usará el visor de artículos en lugar del navegador interno"</string>
<string name="prefer_article_viewer_off">"Se utilizará el navegador interno en lugar del visor de artículo"</string>
<string name="pref_article_viewer_title">"Abrir enlaces dentro de la aplicación"</string>
<string name="pref_article_viewer_on">"Los artículos se abrirán dentro de la aplicación"</string>
<string name="pref_article_viewer_off">"Los artículos se abrirán con tu navegador predeterminado"</string>
<string name="pref_general_category_links">"Control de enlaces"</string>
<string name="pref_general_category_displaying">"Mostrando"</string>
<string name="pref_switch_card_view_on">"Los artículos se mostrarán como tarjetas"</string>
@ -82,17 +62,12 @@
<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="cant_mark_read">No puede marcar el artículo como leído</string>
<string name="cant_mark_unread">No se puede marcar el artículo como no leído</string>
<string name="drawer_error_loading_tags">Error al cargar etiquetas…</string>
<string name="drawer_error_loading_sources">Error al cargar fuentes…</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="no_tags_loaded">No hay etiquetas cargadas</string>
<string name="no_sources_loaded">No hay fuentes cargadas</string>
<string name="drawer_loading">Cargando…</string>
<string name="menu_home_search">Buscar</string>
<string name="can_delete_source">No se puede eliminar la fuente…</string>
@ -100,16 +75,9 @@
<string name="pref_header_theme">Temas</string>
<string name="default_theme">Predeterminado</string>
<string name="default_dark_theme">Predeterminado/Oscuro</string>
<string name="pref_header_debug">Depurar</string>
<string name="self_hosted_cert_switch">Utilizando un certificado alojado propiamente ?</string>
<string name="self_signed_cert_warning">Por razones de seguridad, los certificados propios no son compatibles por defecto. Activando esto, no seré responsable de cualquier problema de seguridad que encuentre.</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="summary_debug_identifier">Identificador de depuración</string>
<string name="unique_id_to_clipboard">Identificador copiado a su portapapeles</string>
<string name="display_header_drawer_summary">Mostrar una cabecera con la url de instancia de selfoss en el cajón lateral.</string>
<string name="display_header_drawer_title">Cabecera de cuenta</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>
@ -120,19 +88,12 @@
<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>
<string name="add_to_favs_reader">Añadir a Favoritos</string>
<string name="remove_to_favs_reader">Eliminar de favoritos</string>
<string name="pref_content_reader_font_size">Tamaño de la fuente del lector</string>
<string name="pref_header_viewer">Visor de artículos</string>
<string name="refresh_dialog_message">Esto actualizará su instancia de Selfoss.</string>
<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="pref_acra_alwaysaccept">Enviar automáticamente informe de fallos</string>
<string name="pref_acra_alwaysaccept_enabled">Se enviaran automáticamente los informes de fallos</string>
<string name="pref_acra_alwaysaccept_disabled">Le preguntará al enviar informes de fallos.</string>
<string name="pref_debug_crash_reports">Informe de fallos</string>
<string name="pref_debug_debug_logs">Registro de depuración (éstos se enviarán sin diálogo)</string>
<string name="acra_login">Habilitar el registro</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>
@ -141,7 +102,8 @@
<string name="pref_switch_items_caching">Guardar elementos para uso sin conexión</string>
<string name="pref_switch_update_sources">Check for new sources and tags</string>
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
<string name="no_network_connectivity">Sin conexión!</string>
<string name="network_connectivity_lost">"Network connection lost"</string>
<string name="network_connectivity_retrieved">"Network connection is now available"</string>
<string name="pref_switch_periodic_refresh">Sincronizar artículos</string>
<string name="pref_switch_periodic_refresh_off">Los artículos no se sincronizarán en segundo plano</string>
<string name="pref_switch_periodic_refresh_on">Los artículos se sincronizarán periódicamente</string>
@ -166,4 +128,9 @@
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string>
<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="drawer_error_loading_sources">Error loading sources…</string>
</resources>

View File

@ -3,16 +3,13 @@
<string name="app_name">"Reader for Selfoss"</string>
<string name="title_activity_login">"Log in"</string>
<string name="prompt_password">"Password"</string>
<string name="prompt_http_password">"HTTP Password"</string>
<string name="action_sign_in">"Go"</string>
<string name="error_invalid_password">"Password not long enough"</string>
<string name="error_field_required">"Field required"</string>
<string name="prompt_url">"Url"</string>
<string name="withLoginSwitch">"Login required ?"</string>
<string name="withHttpLoginSwitch">"HTTP 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="prompt_http_login">"HTTP Username"</string>
<string name="label_share">"Share"</string>
<string name="readAll">"Read all"</string>
<string name="action_disconnect">"Disconnect"</string>
@ -26,9 +23,6 @@
<string name="wrong_infos">"Check your details again."</string>
<string name="all_posts_not_read">"All posts weren't read"</string>
<string name="all_posts_read">"All posts were read"</string>
<string name="cant_get_favs">"Can't get favorites"</string>
<string name="cant_get_new_elements">"Can't get new articles"</string>
<string name="cant_get_read">"Can't get read articles"</string>
<string name="nothing_here">"Nothing here"</string>
<string name="tab_new">"New"</string>
<string name="tab_read">"All"</string>
@ -40,36 +34,22 @@
<string name="addStringNoUrl">"Log in to add sources."</string>
<string name="cant_get_sources">"Can't get sources list."</string>
<string name="cant_create_source">"Can't create source."</string>
<string name="cant_get_spouts">"Can't get spouts list."</string>
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
<string name="cant_get_spouts">"Can't get spouts list. There may ben an api issue."</string>
<string name="form_not_complete">"The form is not complete"</string>
<string name="pref_header_links">"Links"</string>
<string name="issue_tracker_link">"Issue Tracker"</string>
<string name="issue_tracker_summary">"Report a bug or ask for a new feature"</string>
<string name="warning_wrong_url">"WARNING"</string>
<string name="pref_switch_card_view_title">"Card View"</string>
<string name="cant_mark_favortie">"Can't mark article as favorite"</string>
<string name="cant_unmark_favortie">"Can't remove item from favorite"</string>
<string name="share">"Share"</string>
<string name="rating_prompt_title">"Enjoying the app ?"</string>
<string name="rating_prompt_yes">"Yes !"</string>
<string name="rating_prompt_no">"Not really …"</string>
<string name="rating_prompt_feedback_title">"Can you tell us why ?"</string>
<string name="rating_prompt_feedback_yes">"OK !"</string>
<string name="rating_prompt_feedback_no">"Not now."</string>
<string name="rating_prompt_rating_title">"Great ! Can you rate us on the Store ?"</string>
<string name="rating_prompt_rating_yes">"Sure !"</string>
<string name="rating_prompt_rating_no">"Not right now."</string>
<string name="rating_prompt_thanks">"Thanks, your feedback help enhance the app !"</string>
<string name="switch_unread_count">"Display the unread count as a badge for the bottom bar."</string>
<string name="switch_unread_count_title">"Display unread count"</string>
<string name="display_all_counts_title">"Display count for favorite and read"</string>
<string name="text_wrong_url">"You seem to be trying to use an invalid URL. Make sure it is correct, and if the problem persists, contact me (via the store contact link). Please note that the app needs you to be using Selfoss. You can't access RSS feeds without it."</string>
<string name="pref_general_internal_browser_title">"Open links inside the app"</string>
<string name="pref_general_internal_browser_on">"Articles will open inside the app"</string>
<string name="pref_general_internal_browser_off">"Articles will open with your default browser"</string>
<string name="prefer_article_viewer_title">"Use the article viewer"</string>
<string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string>
<string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string>
<string name="pref_article_viewer_title">"Open links inside the app"</string>
<string name="pref_article_viewer_on">"Articles will open inside the app"</string>
<string name="pref_article_viewer_off">"Articles will open with your default browser"</string>
<string name="pref_general_category_links">"Link handling"</string>
<string name="pref_general_category_displaying">"Displaying"</string>
<string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string>
@ -82,17 +62,12 @@
<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="cant_mark_read">Can\'t mark article as read</string>
<string name="cant_mark_unread">Can\'t mark article as unread</string>
<string name="drawer_error_loading_tags">Error loading tags…</string>
<string name="drawer_error_loading_sources">Error loading sources…</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="no_tags_loaded">No tags loaded</string>
<string name="no_sources_loaded">No sources loaded</string>
<string name="drawer_loading">Loading …</string>
<string name="menu_home_search">Search</string>
<string name="can_delete_source">Can\'t delete the source…</string>
@ -100,16 +75,9 @@
<string name="pref_header_theme">Themes</string>
<string name="default_theme">Default</string>
<string name="default_dark_theme">Default/Dark</string>
<string name="pref_header_debug">Debug</string>
<string name="self_hosted_cert_switch">Using a self hosted certificate ?</string>
<string name="self_signed_cert_warning">Due to security reasons, self signed certificates are not supported by default. By activating this, I\'ll not be responsible of any security problem you encounter.</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="summary_debug_identifier">Debug identifier</string>
<string name="unique_id_to_clipboard">Identifier copied to your clipboard</string>
<string name="display_header_drawer_summary">Display a header with the selfoss instance url on the lateral drawer.</string>
<string name="display_header_drawer_title">Account header</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>
@ -120,19 +88,12 @@
<string name="reader_action_share">Share</string>
<string name="pref_switch_actions_pager_scroll_on">Mark articles as read when swiping between articles.</string>
<string name="add_to_favs_reader">Add to favorites</string>
<string name="remove_to_favs_reader">Remove from favorites</string>
<string name="pref_content_reader_font_size">Article reader content font size</string>
<string name="pref_header_viewer">Article viewer</string>
<string name="refresh_dialog_message">This will refresh your Selfoss instance.</string>
<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="pref_acra_alwaysaccept">Automatically send crash reports</string>
<string name="pref_acra_alwaysaccept_enabled">Will send crash reports automatically</string>
<string name="pref_acra_alwaysaccept_disabled">Will ask everytime when sending crash reports.</string>
<string name="pref_debug_crash_reports">Crash reports</string>
<string name="pref_debug_debug_logs">Debug logging (these will be sent without a dialog)</string>
<string name="acra_login">Enable logging</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>
@ -141,7 +102,8 @@
<string name="pref_switch_items_caching">Save items for offline use</string>
<string name="pref_switch_update_sources">Check for new sources and tags</string>
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
<string name="no_network_connectivity">Not connected !</string>
<string name="network_connectivity_lost">"Network connection lost"</string>
<string name="network_connectivity_retrieved">"Network connection is now available"</string>
<string name="pref_switch_periodic_refresh">Sync articles</string>
<string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string>
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
@ -166,4 +128,9 @@
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string>
<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="drawer_error_loading_sources">Error loading sources…</string>
</resources>

View File

@ -3,16 +3,13 @@
<string name="app_name">"Reader for Selfoss"</string>
<string name="title_activity_login">"Login"</string>
<string name="prompt_password">"Mot de passe"</string>
<string name="prompt_http_password">"Mot de passe HTTP"</string>
<string name="action_sign_in">"Valider"</string>
<string name="error_invalid_password">"Mot de passe trop court"</string>
<string name="error_field_required">"Champ requis"</string>
<string name="prompt_url">"Url Selfoss"</string>
<string name="withLoginSwitch">"Avec login ?"</string>
<string name="withHttpLoginSwitch">"Avec login HTTP ?"</string>
<string name="login_url_problem">"Petit souci. Il manque peut être un / à la fin ?"</string>
<string name="login_url_problem">"Petit souci. Il manque peut-être un / à la fin ?"</string>
<string name="prompt_login">"Utilisateur"</string>
<string name="prompt_http_login">"Utilisateur HTTP"</string>
<string name="label_share">"Partager"</string>
<string name="readAll">"Tout lire"</string>
<string name="action_disconnect">"Déconnecter"</string>
@ -26,9 +23,6 @@
<string name="wrong_infos">"Vérifiez vos informations."</string>
<string name="all_posts_not_read">"Tous les posts n'ont pas été lus"</string>
<string name="all_posts_read">"Tous les posts sont lus"</string>
<string name="cant_get_favs">"Impossible de récupérer les éléments favoris."</string>
<string name="cant_get_new_elements">"Impossible de récupérer les nouveaux éléments."</string>
<string name="cant_get_read">"Impossible de récupérer les éléments lus."</string>
<string name="nothing_here">"Il n'y a rien ici !"</string>
<string name="tab_new">"Non lus"</string>
<string name="tab_read">"Tous"</string>
@ -40,36 +34,22 @@
<string name="addStringNoUrl">"Identifiez-vous pour ajouter une source."</string>
<string name="cant_get_sources">"Impossible de récupérer la liste des sources"</string>
<string name="cant_create_source">"Impossible de créer la source."</string>
<string name="cant_get_spouts">"Impossible de récupérer vos Spouts pour rajouter des sources"</string>
<string name="cant_get_spouts_no_network">"Impossible d'obtenir la liste des spouts en raison d'un problème de réseau."</string>
<string name="cant_get_spouts">"Impossible d'obtenir la liste des spouts. Cela pourrait venir de l'api."</string>
<string name="form_not_complete">"Il manque des données. Terminez le formulaire."</string>
<string name="pref_header_links">"Liens utiles"</string>
<string name="issue_tracker_link">"Suivi des problèmes"</string>
<string name="issue_tracker_summary">"Pour signaler un bug ou demander une nouvelle fonctionnalité"</string>
<string name="warning_wrong_url">"ATTENTION"</string>
<string name="pref_switch_card_view_title">"Card View"</string>
<string name="cant_mark_favortie">"Impossible de marquer l'élément comme favoris"</string>
<string name="cant_unmark_favortie">"Impossible de retirer l'élément des favoris"</string>
<string name="pref_switch_card_view_title">"Vue en carte"</string>
<string name="share">"Partager"</string>
<string name="rating_prompt_title">"Vous aimez l'application ?"</string>
<string name="rating_prompt_yes">"Oui."</string>
<string name="rating_prompt_no">"Pas vraiment …"</string>
<string name="rating_prompt_feedback_title">"Pouvez-vous nous dire pourquoi ?"</string>
<string name="rating_prompt_feedback_yes">"Oui !"</string>
<string name="rating_prompt_feedback_no">"Pas maintenant."</string>
<string name="rating_prompt_rating_title">"Super ! Pouvez-vous nous noter sur le store ?"</string>
<string name="rating_prompt_rating_yes">"D'accord !"</string>
<string name="rating_prompt_rating_no">"Peut-être plus tard."</string>
<string name="rating_prompt_thanks">"Merci, cela nous aide à améliorer l'application !"</string>
<string name="switch_unread_count">"Afficher le nombre d'articles non lus sur la barre en bas de l'écran"</string>
<string name="switch_unread_count_title">"Afficher le nombre de non lus"</string>
<string name="display_all_counts_title">"Afficher le nombre de favoris et d'articles lus"</string>
<string name="text_wrong_url">"Vous semblez essayer de vous connecter avec une URL invalide. Assurez-vous que c'est la bonne, et si le problème persiste, contactez-moi via le lien du play store. Notez aussi que l'application ne peut fonctionner sans l'application web Selfoss. Vous ne pouvez pas utiliser l'application pour accéder directement aux flux RSS."</string>
<string name="pref_general_internal_browser_title">"Ouvrir les liens dans l'application"</string>
<string name="pref_general_internal_browser_on">"Les articles s'ouvriront dans l'application"</string>
<string name="pref_general_internal_browser_off">"Les articles s'ouvriront dans votre naviguateur par défaut"</string>
<string name="prefer_article_viewer_title">"Utiliser le visionneur d'articles"</string>
<string name="prefer_article_viewer_on">"Utiliser le naviguateur interne"</string>
<string name="prefer_article_viewer_off">"Utiliser le naviguateur interne au lieu du visionneur d'articles"</string>
<string name="pref_article_viewer_title">"Ouvrir les liens dans l'application"</string>
<string name="pref_article_viewer_on">"Les articles s'ouvriront dans l'application"</string>
<string name="pref_article_viewer_off">"Les articles s'ouvriront dans votre naviguateur par défaut"</string>
<string name="pref_general_category_links">"Gestion des liens"</string>
<string name="pref_general_category_displaying">"Affichage"</string>
<string name="pref_switch_card_view_on">"Les articles seront affichés en forme de carte"</string>
@ -82,17 +62,12 @@
<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="cant_mark_read">Impossible de marquer l\'article comme lu</string>
<string name="cant_mark_unread">Impossible de marquer l\'article comme non lu</string>
<string name="drawer_error_loading_tags">Erreur lors du chargement des tags…</string>
<string name="drawer_error_loading_sources">Erreur lors du chargement des sources…</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="no_tags_loaded">Pas de tags chargés</string>
<string name="no_sources_loaded">Pas de sources chargés</string>
<string name="drawer_loading">Chargement …</string>
<string name="menu_home_search">Rechercher</string>
<string name="can_delete_source">Impossible de supprimer la source…</string>
@ -100,16 +75,9 @@
<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_header_debug">Debug</string>
<string name="self_hosted_cert_switch">Certificat auto-signé ?</string>
<string name="self_signed_cert_warning">Pour des raisons de sécurité, les certificats auto-signés sont désactivés par défaut. En les activant, je ne serais pas responsable de quelconques problèmes de sécurité rencontrés.</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="summary_debug_identifier">Identifiant de debug</string>
<string name="unique_id_to_clipboard">Texte copié</string>
<string name="display_header_drawer_summary">Afficher une entête avec l\'url de votre instance de Selfoss en haut du drawer lateral.</string>
<string name="display_header_drawer_title">Entête de compte</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>
@ -120,19 +88,12 @@
<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>
<string name="add_to_favs_reader">Ajouter aux favoris</string>
<string name="remove_to_favs_reader">Supprimer des favoris</string>
<string name="pref_content_reader_font_size">Taille du texte du contenu du lecteur d\'articles</string>
<string name="pref_header_viewer">Lecteur d\'articles</string>
<string name="refresh_dialog_message">En validant, votre instance Selfoss sera mise à jour.</string>
<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="pref_acra_alwaysaccept">Envoyer automatiquement les rapports d\'erreur</string>
<string name="pref_acra_alwaysaccept_enabled">Enverra automatiquement les rapports d\'erreur</string>
<string name="pref_acra_alwaysaccept_disabled">Demandera une confirmation à chaque incident.</string>
<string name="pref_debug_crash_reports">Rapport d\'erreur</string>
<string name="pref_debug_debug_logs">Log de debug (seront envoyés automatiquement)</string>
<string name="acra_login">Activer les logs</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>
@ -141,7 +102,8 @@
<string name="pref_switch_items_caching">Sauvegarder les articles pour une utilisation hors ligne</string>
<string name="pref_switch_update_sources">Vérifier les nouvelles sources et tags</string>
<string name="pref_switch_update_sources_summary">Désactivez cette option si votre serveur reçoit trop de requêtes.</string>
<string name="no_network_connectivity">Hors connexion !</string>
<string name="network_connectivity_lost">"Connexion au réseau perdue"</string>
<string name="network_connectivity_retrieved">"Connexion réseau de nouveau disponible"</string>
<string name="pref_switch_periodic_refresh">Synchroniser les articles</string>
<string name="pref_switch_periodic_refresh_off">Les articles ne seront pas synchronisés en arrière plan</string>
<string name="pref_switch_periodic_refresh_on">Articles seront périodiquement synchronisées</string>
@ -166,4 +128,9 @@
<string name="reader_static_bar_on">La barre sera affichée</string>
<string name="reader_static_bar_off">La barre sera affichée grâce au bouton</string>
<string name="remove_source">Supprimer la source</string>
<string name="pref_theme_title">Thème Clair/Sombre</string>
<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="drawer_error_loading_sources">Error loading sources…</string>
</resources>

View File

@ -3,16 +3,13 @@
<string name="app_name">"Lector para selfoss"</string>
<string name="title_activity_login">"Conectar"</string>
<string name="prompt_password">"Contrasinal"</string>
<string name="prompt_http_password">"Contrasinal HTTP"</string>
<string name="action_sign_in">"Ir"</string>
<string name="error_invalid_password">"O contrasinal non é suficientemente longo"</string>
<string name="error_field_required">"Campo requirido"</string>
<string name="prompt_url">"URL"</string>
<string name="withLoginSwitch">"É preciso iniciar sesión?"</string>
<string name="withHttpLoginSwitch">"É 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="prompt_http_login">"Nome de usuario HTTP"</string>
<string name="label_share">"Compartir"</string>
<string name="readAll">"Ler todos"</string>
<string name="action_disconnect">"Desconectar"</string>
@ -26,9 +23,6 @@
<string name="wrong_infos">"Comprobar os teus detalles de novo."</string>
<string name="all_posts_not_read">"Non se leron todas as publicacións"</string>
<string name="all_posts_read">"Leronse todas as publicacións"</string>
<string name="cant_get_favs">"Non se poden obter os favoritos"</string>
<string name="cant_get_new_elements">"Non se poden recibir os novos artigos"</string>
<string name="cant_get_read">"Non se poden recibir os artigos lidos"</string>
<string name="nothing_here">"Non hai nada aquí"</string>
<string name="tab_new">"Novo"</string>
<string name="tab_read">"Todos"</string>
@ -40,36 +34,22 @@
<string name="addStringNoUrl">"Accede pra engadir fontes."</string>
<string name="cant_get_sources">"Non se pode obter a lista de fontes."</string>
<string name="cant_create_source">"Non se pode crear unha fonte."</string>
<string name="cant_get_spouts">"Non se pode obter a lista de fontes."</string>
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
<string name="cant_get_spouts">"Can't get spouts list. There may ben an api issue."</string>
<string name="form_not_complete">"O formulario non está completo"</string>
<string name="pref_header_links">"Ligazóns"</string>
<string name="issue_tracker_link">"Rastrexador de Incidencias"</string>
<string name="issue_tracker_summary">"Informar dun erro ou pedir unha nova característica"</string>
<string name="warning_wrong_url">"AVISO"</string>
<string name="pref_switch_card_view_title">"Vista de tarxeta"</string>
<string name="cant_mark_favortie">"Non se pode marcar o artigo como favorito"</string>
<string name="cant_unmark_favortie">"Non se pode eliminar o elemento dos favoritos"</string>
<string name="share">"Compartir"</string>
<string name="rating_prompt_title">"Estás gozando coa aplicación?"</string>
<string name="rating_prompt_yes">"Si !"</string>
<string name="rating_prompt_no">"Non moito …"</string>
<string name="rating_prompt_feedback_title">"Podes dicirnos por qué?"</string>
<string name="rating_prompt_feedback_yes">"Dacordo!"</string>
<string name="rating_prompt_feedback_no">"Agora non."</string>
<string name="rating_prompt_rating_title">"Xenial! Podes puntuarnos na tenda?"</string>
<string name="rating_prompt_rating_yes">"Claro!"</string>
<string name="rating_prompt_rating_no">"Agora mesmo non."</string>
<string name="rating_prompt_thanks">"Grazas, a túa opinión axudanos a mellorar a aplicación!"</string>
<string name="switch_unread_count">"Mostrar o reconto de artigos non lidos cunha insignia na barra inferior."</string>
<string name="switch_unread_count_title">"Mostrar reconto de artigos non lidos"</string>
<string name="display_all_counts_title">"Mostrar reconto de artigos lidos e favoritos"</string>
<string name="text_wrong_url">"Semella que intentas usar unha URL non válida. Asegúrate de que é correcta, e se o problema persiste, ponte en contacto conmigo (a través da ligazón de contacto na tenda). Por favor ten en conta que a aplicación precisa que uses Selfoss. Non podes acceder a canles RSS se non o tes."</string>
<string name="pref_general_internal_browser_title">"Abrir ligazóns dentro da aplicación"</string>
<string name="pref_general_internal_browser_on">"Os artigos abriranse dentro da aplicación"</string>
<string name="pref_general_internal_browser_off">"Os artigos abriranse co teu navegador prederminado"</string>
<string name="prefer_article_viewer_title">"Usar o visor de artigos"</string>
<string name="prefer_article_viewer_on">"Usarase o visor de artigos en lugar do navegador interno"</string>
<string name="prefer_article_viewer_off">"Usarase o navegador interno en lugar do visor de artigos"</string>
<string name="pref_article_viewer_title">"Abrir ligazóns dentro da aplicación"</string>
<string name="pref_article_viewer_on">"Os artigos abriranse dentro da aplicación"</string>
<string name="pref_article_viewer_off">"Os artigos abriranse co teu navegador prederminado"</string>
<string name="pref_general_category_links">"Xestión de ligazóns"</string>
<string name="pref_general_category_displaying">"Visualización"</string>
<string name="pref_switch_card_view_on">"Os artigos amosaranse coma tarxetas"</string>
@ -82,17 +62,12 @@
<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="cant_mark_read">Non se pode marcar o artigo como lido</string>
<string name="cant_mark_unread">Non se pode marcar o artigo como non lido</string>
<string name="drawer_error_loading_tags">Produciuse un erro ao cargar as etiquetas…</string>
<string name="drawer_error_loading_sources">Produciuse un erro ao cargar as fontes…</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="no_tags_loaded">Non se cargou ningunha etiqueta</string>
<string name="no_sources_loaded">Non se cargou ningunha fonte</string>
<string name="drawer_loading">Cargando…</string>
<string name="menu_home_search">Procurar</string>
<string name="can_delete_source">Non se puido eliminar a fonte…</string>
@ -100,16 +75,9 @@
<string name="pref_header_theme">Temas</string>
<string name="default_theme">Predeterminado</string>
<string name="default_dark_theme">Predeterminado/Escuro</string>
<string name="pref_header_debug">Depuración</string>
<string name="self_hosted_cert_switch">Utilizas un certificado autoaloxado?</string>
<string name="self_signed_cert_warning">Por razóns de seguridade, por defecto non se permiten os certificados autoasinados. Activando isto, non serei responsable de calquera problema de seguridade que atopes.</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="summary_debug_identifier">Identificador de depuración</string>
<string name="unique_id_to_clipboard">Copiouse o identificador ao portapapeis</string>
<string name="display_header_drawer_summary">Amosar unha cabeceira coa URL da instancia de Selfoss no panel lateral.</string>
<string name="display_header_drawer_title">Cabeceira da conta</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>
@ -120,19 +88,12 @@
<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>
<string name="add_to_favs_reader">Engadir a favoritos</string>
<string name="remove_to_favs_reader">Eliminar dos favoritos</string>
<string name="pref_content_reader_font_size">Tamaño da fonte do lector</string>
<string name="pref_header_viewer">Visor de artigos</string>
<string name="refresh_dialog_message">Isto actualizará a súa instancia de Selfoss.</string>
<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="pref_acra_alwaysaccept">Enviar automáticamente informes de erros</string>
<string name="pref_acra_alwaysaccept_enabled">Enviaranse automáticamente os informes de erros</string>
<string name="pref_acra_alwaysaccept_disabled">Preguntarase cada vez pra enviar os informes de erros.</string>
<string name="pref_debug_crash_reports">Informes de erros</string>
<string name="pref_debug_debug_logs">Rexistro de depuración (Estes enviaranse automáticamente)</string>
<string name="acra_login">Habilitar o rexistro</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>
@ -141,7 +102,8 @@
<string name="pref_switch_items_caching">Gardar elementos para uso sen conexión</string>
<string name="pref_switch_update_sources">Comproba novas fontes e etiquetas</string>
<string name="pref_switch_update_sources_summary">Deshabilita isto se o teu servidor está recibindo demasiadas peticións de base de datos.</string>
<string name="no_network_connectivity">Non conectado!</string>
<string name="network_connectivity_lost">"Perdeuse a conexión de rede"</string>
<string name="network_connectivity_retrieved">"Conexión de rede xa dispoñíbel"</string>
<string name="pref_switch_periodic_refresh">Sincronizar artigos</string>
<string name="pref_switch_periodic_refresh_off">Os artigos non se sincronizarán coa aplicación de fondo</string>
<string name="pref_switch_periodic_refresh_on">Os artigos sincronizaranse periódicamente</string>
@ -166,4 +128,9 @@
<string name="reader_static_bar_on">A barra inferior mostrarase sempre</string>
<string name="reader_static_bar_off">A barra inferior pode mostrarse a través do botón flotante</string>
<string name="remove_source">Eliminar fonte</string>
<string name="pref_theme_title">Light/Dark mode</string>
<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="drawer_error_loading_sources">Error loading sources…</string>
</resources>

View File

@ -3,16 +3,13 @@
<string name="app_name">"Reader for Selfoss"</string>
<string name="title_activity_login">"Masuk"</string>
<string name="prompt_password">"Kata sandi"</string>
<string name="prompt_http_password">"Kata sandi HTTP"</string>
<string name="action_sign_in">"Mulai"</string>
<string name="error_invalid_password">"Kata sandinya tidak cukup panjang"</string>
<string name="error_field_required">"Kolom wajib diisi"</string>
<string name="prompt_url">"URL"</string>
<string name="withLoginSwitch">"Harus masuk?"</string>
<string name="withHttpLoginSwitch">"Otentikasi HTTP diperlukan?"</string>
<string name="login_url_problem">"Ups. Anda mungkin harus menambahkan \"/\" di akhir url."</string>
<string name="prompt_login">"Nama pengguna"</string>
<string name="prompt_http_login">"Nama pengguna HTTP"</string>
<string name="label_share">"Bagikan"</string>
<string name="readAll">"Baca semua"</string>
<string name="action_disconnect">"Putuskan sambungan"</string>
@ -26,9 +23,6 @@
<string name="wrong_infos">"Periksa kembali detail Anda."</string>
<string name="all_posts_not_read">"Semua pos belum dibaca"</string>
<string name="all_posts_read">"Semua pos sudah dibaca"</string>
<string name="cant_get_favs">"Gagal menuju favorit"</string>
<string name="cant_get_new_elements">"Gagal ke artikel baru"</string>
<string name="cant_get_read">"Gagal ke artikel yang dibaca"</string>
<string name="nothing_here">"Tidak ada di sini"</string>
<string name="tab_new">"Baru"</string>
<string name="tab_read">"Semua"</string>
@ -40,36 +34,22 @@
<string name="addStringNoUrl">"Masuk untuk menambah sumber."</string>
<string name="cant_get_sources">"Tidak bisa mendapatkan daftar sumber."</string>
<string name="cant_create_source">"Tidak dapat membuat sumber."</string>
<string name="cant_get_spouts">"Tidak bisa masuk ke daftar Spouts."</string>
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
<string name="cant_get_spouts">"Can't get spouts list. There may ben an api issue."</string>
<string name="form_not_complete">"Formulirnya belum selesai"</string>
<string name="pref_header_links">"Tautan"</string>
<string name="issue_tracker_link">"Pelacak Masalah"</string>
<string name="issue_tracker_summary">"Laporkan bug atau meminta fitur baru"</string>
<string name="warning_wrong_url">"PERINGATAN"</string>
<string name="pref_switch_card_view_title">"Tampilan Kartu"</string>
<string name="cant_mark_favortie">"Tidak dapat menandai artikel sebagai favorit"</string>
<string name="cant_unmark_favortie">"Tidak dapat melepas item dari favorit"</string>
<string name="share">"Bagikan"</string>
<string name="rating_prompt_title">"Suka aplikasi ini?"</string>
<string name="rating_prompt_yes">"Ya !"</string>
<string name="rating_prompt_no">"Tidak suka …"</string>
<string name="rating_prompt_feedback_title">"Bisakah Anda memberitahu kami alasannya?"</string>
<string name="rating_prompt_feedback_yes">"Oke !"</string>
<string name="rating_prompt_feedback_no">"Tidak sekarang."</string>
<string name="rating_prompt_rating_title">"Bagus! Dapatkah Anda memberi nilai kami di Store ?"</string>
<string name="rating_prompt_rating_yes">"Tentu saja !"</string>
<string name="rating_prompt_rating_no">"Jangan sekarang."</string>
<string name="rating_prompt_thanks">"Terima kasih, umpan balik Anda membantu pengembangan aplikasi !"</string>
<string name="switch_unread_count">"Tampilkan jumlah item yang belum dibaca di bilah bawah."</string>
<string name="switch_unread_count_title">"Tampilkan jumlah item yang belum dibaca"</string>
<string name="display_all_counts_title">"Tampilkan jumlah item untuk favorit dan sudah dibaca"</string>
<string name="text_wrong_url">"Sepertinya Anda mencoba menggunakan URL yang tidak valid. Pastikan itu benar, jika masalah terus berlanjut, hubungi saya (melalui link kontak toko). Harap dicatat bahwa aplikasi ini mengharuskan Anda menggunakan Selfoss. Tanpa itu, Anda tidak bisa mengakses umpan RSS."</string>
<string name="pref_general_internal_browser_title">"Buka tautan dalam aplikasi"</string>
<string name="pref_general_internal_browser_on">"Artikel akan dibuka di dalam aplikasi"</string>
<string name="pref_general_internal_browser_off">"Artikel akan dibuka dalam peramban bawaan Anda"</string>
<string name="prefer_article_viewer_title">"Gunakan pratinjau artikel"</string>
<string name="prefer_article_viewer_on">"Lihat artikel di penampil daripada peramban internal"</string>
<string name="prefer_article_viewer_off">"Gunakan peramban internal dan bukan penampil artikel"</string>
<string name="pref_article_viewer_title">"Buka tautan dalam aplikasi"</string>
<string name="pref_article_viewer_on">"Artikel akan dibuka di dalam aplikasi"</string>
<string name="pref_article_viewer_off">"Artikel akan dibuka dalam peramban bawaan Anda"</string>
<string name="pref_general_category_links">"Pengolahan tautan"</string>
<string name="pref_general_category_displaying">"Tampilan"</string>
<string name="pref_switch_card_view_on">"Artikel ini akan ditampilkan dalam bentuk kartu"</string>
@ -82,17 +62,12 @@
<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="cant_mark_read">Tidak dapat menandai artikel sebagai telah dibaca</string>
<string name="cant_mark_unread">Can\'t mark article as unread</string>
<string name="drawer_error_loading_tags">Kesalahan saat memuat tag…</string>
<string name="drawer_error_loading_sources">Kesalahan saat memuat sumber…</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="no_tags_loaded">Tidak ada tag yang dimuat</string>
<string name="no_sources_loaded">Tak ada sumber yang dimuat</string>
<string name="drawer_loading">Memuat …</string>
<string name="menu_home_search">Cari</string>
<string name="can_delete_source">Tidak dapat menghapus sumber…</string>
@ -100,16 +75,9 @@
<string name="pref_header_theme">Tema</string>
<string name="default_theme">Bawaan</string>
<string name="default_dark_theme">Bawaan/Gelap</string>
<string name="pref_header_debug">Debug</string>
<string name="self_hosted_cert_switch">Sertifikat yang ditandatangani sendiri?</string>
<string name="self_signed_cert_warning">Untuk alasan keamanan, sertifikat yang ditandatangani sendiri tidak didukung secara bawaan. Jika Anda mengaktifkan item ini, saya tidak akan bertanggung jawab atas masalah keamanan yang Anda hadapi.</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="summary_debug_identifier">Identifikasi debug</string>
<string name="unique_id_to_clipboard">Salin pengenal ke papan klip Anda</string>
<string name="display_header_drawer_summary">Kop dengan alamat link Selfoss ditampilkan di laci lateral.</string>
<string name="display_header_drawer_title">Kop akun</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>
@ -120,19 +88,12 @@
<string name="reader_action_share">Bagikan</string>
<string name="pref_switch_actions_pager_scroll_on">Mark articles as read when swiping between articles.</string>
<string name="add_to_favs_reader">Add to favorites</string>
<string name="remove_to_favs_reader">Remove from favorites</string>
<string name="pref_content_reader_font_size">Article reader content font size</string>
<string name="pref_header_viewer">Article viewer</string>
<string name="refresh_dialog_message">This will refresh your Selfoss instance.</string>
<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="pref_acra_alwaysaccept">Automatically send crash reports</string>
<string name="pref_acra_alwaysaccept_enabled">Will send crash reports automatically</string>
<string name="pref_acra_alwaysaccept_disabled">Will ask everytime when sending crash reports.</string>
<string name="pref_debug_crash_reports">Crash reports</string>
<string name="pref_debug_debug_logs">Debug logging (these will be sent without a dialog)</string>
<string name="acra_login">Enable logging</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>
@ -141,7 +102,8 @@
<string name="pref_switch_items_caching">Save items for offline use</string>
<string name="pref_switch_update_sources">Check for new sources and tags</string>
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
<string name="no_network_connectivity">Not connected !</string>
<string name="network_connectivity_lost">"Network connection lost"</string>
<string name="network_connectivity_retrieved">"Network connection is now available"</string>
<string name="pref_switch_periodic_refresh">Sync articles</string>
<string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string>
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
@ -166,4 +128,9 @@
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string>
<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="drawer_error_loading_sources">Error loading sources…</string>
</resources>

View File

@ -3,16 +3,13 @@
<string name="app_name">"Lettore RSS per Selfoss"</string>
<string name="title_activity_login">"Accedi"</string>
<string name="prompt_password">"Password"</string>
<string name="prompt_http_password">"Password HTTP"</string>
<string name="action_sign_in">"Vai"</string>
<string name="error_invalid_password">"La password non è sufficientemente lunga"</string>
<string name="error_field_required">"Campo obbligatorio"</string>
<string name="prompt_url">"URL"</string>
<string name="withLoginSwitch">"È richiesto l'accesso?"</string>
<string name="withHttpLoginSwitch">"Accesso HTTP necessario?"</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="prompt_http_login">"Nome utente HTTP"</string>
<string name="label_share">"Condividi"</string>
<string name="readAll">"Segna tutte come lette"</string>
<string name="action_disconnect">"Scollegati"</string>
@ -26,9 +23,6 @@
<string name="wrong_infos">"Controlla nuovamente i dati."</string>
<string name="all_posts_not_read">"All posts weren't read"</string>
<string name="all_posts_read">"Tutti i messaggi sono stati letti"</string>
<string name="cant_get_favs">"Non è possibile ottenere i preferiti"</string>
<string name="cant_get_new_elements">"Non è possibile ottenere nuovi articoli"</string>
<string name="cant_get_read">"Can't get read articles"</string>
<string name="nothing_here">"Non c'è niente qui"</string>
<string name="tab_new">"Nuovi"</string>
<string name="tab_read">"Tutti"</string>
@ -40,36 +34,22 @@
<string name="addStringNoUrl">"Autenticati per aggiungere fonti."</string>
<string name="cant_get_sources">"Can't get sources list."</string>
<string name="cant_create_source">"Can't create source."</string>
<string name="cant_get_spouts">"Can't get spouts list."</string>
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
<string name="cant_get_spouts">"Can't get spouts list. There may ben an api issue."</string>
<string name="form_not_complete">"Il modulo non è completo"</string>
<string name="pref_header_links">"Links"</string>
<string name="issue_tracker_link">"Traccia problemi"</string>
<string name="issue_tracker_summary">"Segnalare un bug o richiedere una nuova funzionalità"</string>
<string name="warning_wrong_url">"ATTENZIONE"</string>
<string name="pref_switch_card_view_title">"Visualizzazione a schede"</string>
<string name="cant_mark_favortie">"Can't mark article as favorite"</string>
<string name="cant_unmark_favortie">"Can't remove item from favorite"</string>
<string name="share">"Share"</string>
<string name="rating_prompt_title">"Enjoying the app ?"</string>
<string name="rating_prompt_yes">"Yes !"</string>
<string name="rating_prompt_no">"Not really …"</string>
<string name="rating_prompt_feedback_title">"Can you tell us why ?"</string>
<string name="rating_prompt_feedback_yes">"OK !"</string>
<string name="rating_prompt_feedback_no">"Not now."</string>
<string name="rating_prompt_rating_title">"Great ! Can you rate us on the Store ?"</string>
<string name="rating_prompt_rating_yes">"Sure !"</string>
<string name="rating_prompt_rating_no">"Not right now."</string>
<string name="rating_prompt_thanks">"Thanks, your feedback help enhance the app !"</string>
<string name="switch_unread_count">"Display the unread count as a badge for the bottom bar."</string>
<string name="switch_unread_count_title">"Display unread count"</string>
<string name="display_all_counts_title">"Display count for favorite and read"</string>
<string name="text_wrong_url">"You seem to be trying to use an invalid URL. Make sure it is correct, and if the problem persists, contact me (via the store contact link). Please note that the app needs you to be using Selfoss. You can't access RSS feeds without it."</string>
<string name="pref_general_internal_browser_title">"Open links inside the app"</string>
<string name="pref_general_internal_browser_on">"Articles will open inside the app"</string>
<string name="pref_general_internal_browser_off">"Articles will open with your default browser"</string>
<string name="prefer_article_viewer_title">"Use the article viewer"</string>
<string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string>
<string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string>
<string name="pref_article_viewer_title">"Open links inside the app"</string>
<string name="pref_article_viewer_on">"Articles will open inside the app"</string>
<string name="pref_article_viewer_off">"Articles will open with your default browser"</string>
<string name="pref_general_category_links">"Link handling"</string>
<string name="pref_general_category_displaying">"Displaying"</string>
<string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string>
@ -82,17 +62,12 @@
<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="cant_mark_read">Impossibile contrassegnare l\'articolo come già letto</string>
<string name="cant_mark_unread">Can\'t mark article as unread</string>
<string name="drawer_error_loading_tags">Errore nel caricamento dei tag…</string>
<string name="drawer_error_loading_sources">Errore nel caricamento delle fonti…</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="no_tags_loaded">Nessun tag caricato</string>
<string name="no_sources_loaded">No sources loaded</string>
<string name="drawer_loading">Caricamento…</string>
<string name="menu_home_search">Cerca</string>
<string name="can_delete_source">Non è possibile eliminare la fonte…</string>
@ -100,16 +75,9 @@
<string name="pref_header_theme">Temi</string>
<string name="default_theme">Predefinito</string>
<string name="default_dark_theme">Predefinito (Scuro)</string>
<string name="pref_header_debug">Debug</string>
<string name="self_hosted_cert_switch">Using a self hosted certificate ?</string>
<string name="self_signed_cert_warning">Due to security reasons, self signed certificates are not supported by default. By activating this, I\'ll not be responsible of any security problem you encounter.</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="summary_debug_identifier">Debug identifier</string>
<string name="unique_id_to_clipboard">Identifier copied to your clipboard</string>
<string name="display_header_drawer_summary">Display a header with the selfoss instance url on the lateral drawer.</string>
<string name="display_header_drawer_title">Account header</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>
@ -120,19 +88,12 @@
<string name="reader_action_share">Share</string>
<string name="pref_switch_actions_pager_scroll_on">Mark articles as read when swiping between articles.</string>
<string name="add_to_favs_reader">Add to favorites</string>
<string name="remove_to_favs_reader">Remove from favorites</string>
<string name="pref_content_reader_font_size">Article reader content font size</string>
<string name="pref_header_viewer">Article viewer</string>
<string name="refresh_dialog_message">This will refresh your Selfoss instance.</string>
<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="pref_acra_alwaysaccept">Automatically send crash reports</string>
<string name="pref_acra_alwaysaccept_enabled">Will send crash reports automatically</string>
<string name="pref_acra_alwaysaccept_disabled">Will ask everytime when sending crash reports.</string>
<string name="pref_debug_crash_reports">Crash reports</string>
<string name="pref_debug_debug_logs">Debug logging (these will be sent without a dialog)</string>
<string name="acra_login">Enable logging</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>
@ -141,7 +102,8 @@
<string name="pref_switch_items_caching">Save items for offline use</string>
<string name="pref_switch_update_sources">Check for new sources and tags</string>
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
<string name="no_network_connectivity">Not connected !</string>
<string name="network_connectivity_lost">"Network connection lost"</string>
<string name="network_connectivity_retrieved">"Network connection is now available"</string>
<string name="pref_switch_periodic_refresh">Sync articles</string>
<string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string>
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
@ -166,4 +128,9 @@
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string>
<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="drawer_error_loading_sources">Error loading sources…</string>
</resources>

View File

@ -3,16 +3,13 @@
<string name="app_name">"Reader for Selfoss"</string>
<string name="title_activity_login">"로그인"</string>
<string name="prompt_password">"비밀번호"</string>
<string name="prompt_http_password">"HTTP 암호"</string>
<string name="action_sign_in">"Go"</string>
<string name="error_invalid_password">"패스워드가 짧습니다."</string>
<string name="error_field_required">"필수 항목"</string>
<string name="prompt_url">"Url"</string>
<string name="withLoginSwitch">"로그인이 필요합니까?"</string>
<string name="withHttpLoginSwitch">"HTTP 로그인이 필요 합니까?"</string>
<string name="login_url_problem">"죄송합니다. Url의 끝에 \"/\"를 추가할 필요가 있습니다."</string>
<string name="prompt_login">"사용자 이름"</string>
<string name="prompt_http_login">"HTTP 사용자 이름"</string>
<string name="label_share">"공유"</string>
<string name="readAll">"모두 읽기"</string>
<string name="action_disconnect">"연결 해제"</string>
@ -26,9 +23,6 @@
<string name="wrong_infos">"세부 정보를 다시 확인하세요."</string>
<string name="all_posts_not_read">"모든 게시물을 읽지 않았습니다."</string>
<string name="all_posts_read">"모든 게시물을 읽었습니다."</string>
<string name="cant_get_favs">"즐겨찾기를 가져올 수 없습니다."</string>
<string name="cant_get_new_elements">"새로운 기사를 가져올 수 없습니다."</string>
<string name="cant_get_read">"읽은 기사를 가져올 수 없습니다."</string>
<string name="nothing_here">"비어있음"</string>
<string name="tab_new">"새로운"</string>
<string name="tab_read">"전체"</string>
@ -40,36 +34,22 @@
<string name="addStringNoUrl">"로그인 소스를 추가 해야 합니다."</string>
<string name="cant_get_sources">"소스 리스트를 얻을 수 없습니다."</string>
<string name="cant_create_source">"소스를 만들 수 없습니다."</string>
<string name="cant_get_spouts">"Spouts 목록을 가져올 수 없습니다."</string>
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
<string name="cant_get_spouts">"Can't get spouts list. There may ben an api issue."</string>
<string name="form_not_complete">"양식이 완료되지 않았습니다."</string>
<string name="pref_header_links">"링크"</string>
<string name="issue_tracker_link">"이슈 트래커"</string>
<string name="issue_tracker_summary">"버그를 보고 하거나 새기능에 대해 요청하세요."</string>
<string name="warning_wrong_url">"경고"</string>
<string name="pref_switch_card_view_title">"카드 형식 보기"</string>
<string name="cant_mark_favortie">"좋아하는 문서를 마크할 수 없습니다."</string>
<string name="cant_unmark_favortie">"좋아하는 항목에서 제거할 수 없습니다."</string>
<string name="share">"공유"</string>
<string name="rating_prompt_title">"이 앱에 만족하십니까?"</string>
<string name="rating_prompt_yes">"예!"</string>
<string name="rating_prompt_no">"설마..."</string>
<string name="rating_prompt_feedback_title">"이유를 우리에게 말해줄 수 있습니까?"</string>
<string name="rating_prompt_feedback_yes">"OK!"</string>
<string name="rating_prompt_feedback_no">"나중에"</string>
<string name="rating_prompt_rating_title">"Great ! Can you rate us on the Store ?"</string>
<string name="rating_prompt_rating_yes">"Sure !"</string>
<string name="rating_prompt_rating_no">"Not right now."</string>
<string name="rating_prompt_thanks">"Thanks, your feedback help enhance the app !"</string>
<string name="switch_unread_count">"Display the unread count as a badge for the bottom bar."</string>
<string name="switch_unread_count_title">"Display unread count"</string>
<string name="display_all_counts_title">"Display count for favorite and read"</string>
<string name="text_wrong_url">"You seem to be trying to use an invalid URL. Make sure it is correct, and if the problem persists, contact me (via the store contact link). Please note that the app needs you to be using Selfoss. You can't access RSS feeds without it."</string>
<string name="pref_general_internal_browser_title">"Open links inside the app"</string>
<string name="pref_general_internal_browser_on">"Articles will open inside the app"</string>
<string name="pref_general_internal_browser_off">"Articles will open with your default browser"</string>
<string name="prefer_article_viewer_title">"Use the article viewer"</string>
<string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string>
<string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string>
<string name="pref_article_viewer_title">"Open links inside the app"</string>
<string name="pref_article_viewer_on">"Articles will open inside the app"</string>
<string name="pref_article_viewer_off">"Articles will open with your default browser"</string>
<string name="pref_general_category_links">"Link handling"</string>
<string name="pref_general_category_displaying">"Displaying"</string>
<string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string>
@ -82,17 +62,12 @@
<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="cant_mark_read">Can\'t mark article as read</string>
<string name="cant_mark_unread">Can\'t mark article as unread</string>
<string name="drawer_error_loading_tags">Error loading tags…</string>
<string name="drawer_error_loading_sources">Error loading sources…</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="no_tags_loaded">No tags loaded</string>
<string name="no_sources_loaded">No sources loaded</string>
<string name="drawer_loading">Loading …</string>
<string name="menu_home_search">Search</string>
<string name="can_delete_source">Can\'t delete the source…</string>
@ -100,16 +75,9 @@
<string name="pref_header_theme">Themes</string>
<string name="default_theme">Default</string>
<string name="default_dark_theme">Default/Dark</string>
<string name="pref_header_debug">Debug</string>
<string name="self_hosted_cert_switch">Using a self hosted certificate ?</string>
<string name="self_signed_cert_warning">Due to security reasons, self signed certificates are not supported by default. By activating this, I\'ll not be responsible of any security problem you encounter.</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="summary_debug_identifier">Debug identifier</string>
<string name="unique_id_to_clipboard">Identifier copied to your clipboard</string>
<string name="display_header_drawer_summary">Display a header with the selfoss instance url on the lateral drawer.</string>
<string name="display_header_drawer_title">Account header</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>
@ -120,19 +88,12 @@
<string name="reader_action_share">Share</string>
<string name="pref_switch_actions_pager_scroll_on">Mark articles as read when swiping between articles.</string>
<string name="add_to_favs_reader">Add to favorites</string>
<string name="remove_to_favs_reader">Remove from favorites</string>
<string name="pref_content_reader_font_size">Article reader content font size</string>
<string name="pref_header_viewer">Article viewer</string>
<string name="refresh_dialog_message">This will refresh your Selfoss instance.</string>
<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="pref_acra_alwaysaccept">Automatically send crash reports</string>
<string name="pref_acra_alwaysaccept_enabled">Will send crash reports automatically</string>
<string name="pref_acra_alwaysaccept_disabled">Will ask everytime when sending crash reports.</string>
<string name="pref_debug_crash_reports">Crash reports</string>
<string name="pref_debug_debug_logs">Debug logging (these will be sent without a dialog)</string>
<string name="acra_login">Enable logging</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>
@ -141,7 +102,8 @@
<string name="pref_switch_items_caching">Save items for offline use</string>
<string name="pref_switch_update_sources">Check for new sources and tags</string>
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
<string name="no_network_connectivity">Not connected !</string>
<string name="network_connectivity_lost">"Network connection lost"</string>
<string name="network_connectivity_retrieved">"Network connection is now available"</string>
<string name="pref_switch_periodic_refresh">Sync articles</string>
<string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string>
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
@ -166,4 +128,9 @@
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string>
<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="drawer_error_loading_sources">Error loading sources…</string>
</resources>

View File

@ -0,0 +1,13 @@
<resources>
<style name="NoBar" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="colorAccentDark">@color/colorAccentDark</item>
<item name="preferenceTheme">@style/PreferenceStyle</item>
<item name="android:statusBarColor">@color/dark</item>
<item name="bottomBarBackground">@color/dark</item>
<item name="toolbarPopupTheme">@style/ThemeOverlay.AppCompat.Dark</item>
<item name="webviewBackground">@color/dark</item>
</style>
</resources>

View File

@ -3,16 +3,13 @@
<string name="app_name">"Selfoss Reader"</string>
<string name="title_activity_login">"Inloggen"</string>
<string name="prompt_password">"Wachtwoord"</string>
<string name="prompt_http_password">"HTTP Wachtwoord"</string>
<string name="action_sign_in">"Inloggen"</string>
<string name="error_invalid_password">"Wachtwoord niet lang genoeg"</string>
<string name="error_field_required">"Dit veld is verplicht"</string>
<string name="prompt_url">"Selfoss server"</string>
<string name="withLoginSwitch">"Authenticatie vereist?"</string>
<string name="withHttpLoginSwitch">"HTTP 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="prompt_http_login">"HTTP Gebruikersnaam"</string>
<string name="label_share">"Delen"</string>
<string name="readAll">"Alles lezen"</string>
<string name="action_disconnect">"Verbinding verbreken"</string>
@ -26,9 +23,6 @@
<string name="wrong_infos">"Controleer de gegevens nogmaals."</string>
<string name="all_posts_not_read">"Fout bij markeren als gelezen"</string>
<string name="all_posts_read">"Alle artikelen gemarkeerd als gelezen"</string>
<string name="cant_get_favs">"Ophalen favorieten mislukt"</string>
<string name="cant_get_new_elements">"Ophalen nieuwe artikelen mislukt"</string>
<string name="cant_get_read">"Ophalen reeds gelezen artikelen mislukt"</string>
<string name="nothing_here">"Niets gevonden"</string>
<string name="tab_new">"Nieuw"</string>
<string name="tab_read">"Alle"</string>
@ -40,36 +34,22 @@
<string name="addStringNoUrl">"Login om bronnen toe te voegen"</string>
<string name="cant_get_sources">"Kan de lijst met bronnen niet ophalen"</string>
<string name="cant_create_source">"Kan bron niet creëeren"</string>
<string name="cant_get_spouts">"Ophalen spouts mislukt"</string>
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
<string name="cant_get_spouts">"Can't get spouts list. There may ben an api issue."</string>
<string name="form_not_complete">"Formulier is niet volledig ingevuld"</string>
<string name="pref_header_links">"Links"</string>
<string name="issue_tracker_link">"Bug tracker"</string>
<string name="issue_tracker_summary">"Rapporteer een probleem of dien een verzoek in"</string>
<string name="warning_wrong_url">"WAARSCHUWING"</string>
<string name="pref_switch_card_view_title">"Kaart weergave"</string>
<string name="cant_mark_favortie">"Kan het artikel niet markeren als favoriet"</string>
<string name="cant_unmark_favortie">"Kan het item niet verwijderen uit favorieten"</string>
<string name="share">"Delen"</string>
<string name="rating_prompt_title">"Bevalt deze app?"</string>
<string name="rating_prompt_yes">"Ja!"</string>
<string name="rating_prompt_no">"Niet echt…"</string>
<string name="rating_prompt_feedback_title">"Kun je ons vertellen waarom?"</string>
<string name="rating_prompt_feedback_yes">"OK!"</string>
<string name="rating_prompt_feedback_no">"Niet nu."</string>
<string name="rating_prompt_rating_title">"Fantastisch! Wil je een review achter laten in de Store?"</string>
<string name="rating_prompt_rating_yes">"Vanzelfsprekend"</string>
<string name="rating_prompt_rating_no">"Niet op dit moment"</string>
<string name="rating_prompt_thanks">"Bedankt, jouw feedback helpt ons de app te verbeteren"</string>
<string name="switch_unread_count">"Geef het aantal ongelezen artikelen weer in de balk onderaan"</string>
<string name="switch_unread_count_title">"Geef aantal ongelezen weer"</string>
<string name="display_all_counts_title">"Geef aantal weer bij favorieten en gelezen"</string>
<string name="text_wrong_url">"De gebruikte link lijkt onjuist. Controleer deze. Mocht het probleem blijven, neem dan contact met me op (via de contact link in de store). Om deze app te kunnen gebruiken heb je Selfoss nodig."</string>
<string name="pref_general_internal_browser_title">"Links opnemen in interne browser"</string>
<string name="pref_general_internal_browser_on">"Artikelen worden in de interne browser geopend"</string>
<string name="pref_general_internal_browser_off">"Artikelen worden geopend in de standaard browser"</string>
<string name="prefer_article_viewer_title">"Gebruik artikel viewer"</string>
<string name="prefer_article_viewer_on">"Artikelen in viewer weergeven in plaats van de interne browser"</string>
<string name="prefer_article_viewer_off">"Artikelen in interne browser weergeven in plaats van viewer"</string>
<string name="pref_article_viewer_title">"Links opnemen in interne browser"</string>
<string name="pref_article_viewer_on">"Artikelen worden in de interne browser geopend"</string>
<string name="pref_article_viewer_off">"Artikelen worden geopend in de standaard browser"</string>
<string name="pref_general_category_links">"Links"</string>
<string name="pref_general_category_displaying">"Weergave"</string>
<string name="pref_switch_card_view_on">"De artikelen worden als kaarten weergegeven"</string>
@ -82,17 +62,12 @@
<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="cant_mark_read">Impossible de marquer l\'article comme lu</string>
<string name="cant_mark_unread">Can\'t mark article as unread</string>
<string name="drawer_error_loading_tags">Fout bij het laden van tags…</string>
<string name="drawer_error_loading_sources">Fout bij laden van bronnen…</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="no_tags_loaded">Geen tags geladen</string>
<string name="no_sources_loaded">Geen bronnen geladen</string>
<string name="drawer_loading">Bezig met laden …</string>
<string name="menu_home_search">Zoeken</string>
<string name="can_delete_source">Kan de bron niet verwijderen…</string>
@ -100,16 +75,9 @@
<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_header_debug">Fout opsporen</string>
<string name="self_hosted_cert_switch">Gebruik een zelf gehost certificaat?</string>
<string name="self_signed_cert_warning">Vanwege veiligheidsredenen worden zelfondertekende certificaten niet standaard ondersteund. Door dit te activeren, ben ik niet verantwoordelijk voor beveiligingsproblemen die u tegenkomt.</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="summary_debug_identifier">ID voor foutopsporing</string>
<string name="unique_id_to_clipboard">ID naar uw klembord gekopieerd</string>
<string name="display_header_drawer_summary">Laat een koptekst weergeven met de url van de selfoss instantie in de zijlade.</string>
<string name="display_header_drawer_title">Account titel</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>
@ -120,19 +88,12 @@
<string name="reader_action_share">Delen</string>
<string name="pref_switch_actions_pager_scroll_on">Mark articles as read when swiping between articles.</string>
<string name="add_to_favs_reader">Add to favorites</string>
<string name="remove_to_favs_reader">Remove from favorites</string>
<string name="pref_content_reader_font_size">Article reader content font size</string>
<string name="pref_header_viewer">Article viewer</string>
<string name="refresh_dialog_message">This will refresh your Selfoss instance.</string>
<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="pref_acra_alwaysaccept">Automatically send crash reports</string>
<string name="pref_acra_alwaysaccept_enabled">Will send crash reports automatically</string>
<string name="pref_acra_alwaysaccept_disabled">Will ask everytime when sending crash reports.</string>
<string name="pref_debug_crash_reports">Crash reports</string>
<string name="pref_debug_debug_logs">Debug logging (these will be sent without a dialog)</string>
<string name="acra_login">Enable logging</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>
@ -141,7 +102,8 @@
<string name="pref_switch_items_caching">Save items for offline use</string>
<string name="pref_switch_update_sources">Check for new sources and tags</string>
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
<string name="no_network_connectivity">Not connected !</string>
<string name="network_connectivity_lost">"Network connection lost"</string>
<string name="network_connectivity_retrieved">"Network connection is now available"</string>
<string name="pref_switch_periodic_refresh">Sync articles</string>
<string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string>
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
@ -166,4 +128,9 @@
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string>
<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="drawer_error_loading_sources">Error loading sources…</string>
</resources>

View File

@ -3,16 +3,13 @@
<string name="app_name">"Reader for Selfoss"</string>
<string name="title_activity_login">"Entrar"</string>
<string name="prompt_password">"Senha"</string>
<string name="prompt_http_password">"Senha HTTP"</string>
<string name="action_sign_in">"Vamos lá"</string>
<string name="error_invalid_password">"Senha muito pequena"</string>
<string name="error_field_required">"Campo obrigatório"</string>
<string name="prompt_url">"Url"</string>
<string name="withLoginSwitch">"É necessário o login ?"</string>
<string name="withHttpLoginSwitch">"É necessário o login HTTP ?"</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="prompt_http_login">"Usuário HTTP"</string>
<string name="label_share">"Compartilhar"</string>
<string name="readAll">"Ler todos"</string>
<string name="action_disconnect">"Desconectar"</string>
@ -26,9 +23,6 @@
<string name="wrong_infos">"Verifique os detalhes novamente."</string>
<string name="all_posts_not_read">"Nenhum post foi lido"</string>
<string name="all_posts_read">"Todos os posts foram lidos"</string>
<string name="cant_get_favs">"Não consigo obter os favoritos"</string>
<string name="cant_get_new_elements">"Não consigo obter novos artigos"</string>
<string name="cant_get_read">"Não consigo ler artigos"</string>
<string name="nothing_here">"Nada aqui"</string>
<string name="tab_new">"Novo"</string>
<string name="tab_read">"Todos"</string>
@ -40,36 +34,22 @@
<string name="addStringNoUrl">"Faça login para adicionar fontes."</string>
<string name="cant_get_sources">"Não é possível obter a lista de fontes."</string>
<string name="cant_create_source">"Não é possível criar fonte."</string>
<string name="cant_get_spouts">"Não é possível obter a lista de spouts."</string>
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
<string name="cant_get_spouts">"Can't get spouts list. There may ben an api issue."</string>
<string name="form_not_complete">"O formulário não está completo"</string>
<string name="pref_header_links">"Links"</string>
<string name="issue_tracker_link">"Rastreador de problemas"</string>
<string name="issue_tracker_summary">"Informe um erro ou peça um novo recurso"</string>
<string name="warning_wrong_url">"ATENÇÃO"</string>
<string name="pref_switch_card_view_title">"Card View"</string>
<string name="cant_mark_favortie">"Não é possível marcar o artigo como favorito"</string>
<string name="cant_unmark_favortie">"Não é possível remover o item do favorito"</string>
<string name="share">"Compartilhar"</string>
<string name="rating_prompt_title">"Aproveitando o app ?"</string>
<string name="rating_prompt_yes">"Sim !"</string>
<string name="rating_prompt_no">"Na verdade não …"</string>
<string name="rating_prompt_feedback_title">"Você pode nos dizer o porquê ?"</string>
<string name="rating_prompt_feedback_yes">"OK !"</string>
<string name="rating_prompt_feedback_no">"Não agora."</string>
<string name="rating_prompt_rating_title">"Ótimo ! Você pode nos avaliar na loja ?"</string>
<string name="rating_prompt_rating_yes">"Com certeza !"</string>
<string name="rating_prompt_rating_no">"Não agora."</string>
<string name="rating_prompt_thanks">"Obrigado, seu comentário ajuda a melhorar o app !"</string>
<string name="switch_unread_count">"Exibir a contagem de artigos não lidos como um badge na barra inferior."</string>
<string name="switch_unread_count_title">"Exibir contagem de artigos não lidos"</string>
<string name="display_all_counts_title">"Exibir contagem de lidos e favoritos"</string>
<string name="text_wrong_url">"Parece que você está tentando utilizar uma URL inválida. Certifique-se de que está correto, e se o problema persistir, entre em contato comigo (através do link de contato da loja). Por favor, note que o aplicativo precisa que você esteja usando o Selfoss. Você não pode acessar feeds RSS sem ele."</string>
<string name="pref_general_internal_browser_title">"Abrir links dentro do aplicativo"</string>
<string name="pref_general_internal_browser_on">"Os artigos serão abertos dentro do aplicativo"</string>
<string name="pref_general_internal_browser_off">"Os artigos serão abertos com seu navegador padrão"</string>
<string name="prefer_article_viewer_title">"Use o visualizador de artigos"</string>
<string name="prefer_article_viewer_on">"Usará o visualizador de artigos em vez do navegador"</string>
<string name="prefer_article_viewer_off">"Utilizará o navegador em vez do visualizador de artigos"</string>
<string name="pref_article_viewer_title">"Abrir links dentro do aplicativo"</string>
<string name="pref_article_viewer_on">"Os artigos serão abertos dentro do aplicativo"</string>
<string name="pref_article_viewer_off">"Os artigos serão abertos com seu navegador padrão"</string>
<string name="pref_general_category_links">"Manipulação de links"</string>
<string name="pref_general_category_displaying">"Mostrando"</string>
<string name="pref_switch_card_view_on">"Os artigos serão exibidos no formato de cards"</string>
@ -82,17 +62,12 @@
<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="cant_mark_read">Não é possível marcar o artigo como lido</string>
<string name="cant_mark_unread">Can\'t mark article as unread</string>
<string name="drawer_error_loading_tags">Erro ao carregar as tags…</string>
<string name="drawer_error_loading_sources">Erro ao carregar as fontes…</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="no_tags_loaded">Nenhuma tag carregada</string>
<string name="no_sources_loaded">Nenhuma fonte carregada</string>
<string name="drawer_loading">Carregando …</string>
<string name="menu_home_search">Procurar</string>
<string name="can_delete_source">Não foi possível apagar a fonte…</string>
@ -100,16 +75,9 @@
<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_header_debug">Depurar</string>
<string name="self_hosted_cert_switch">Usando um certificado autônomo ?</string>
<string name="self_signed_cert_warning">Por motivos de segurança, certificados autônomos não são suportados por padrão. Ao ativar, não serei responsável por qualquer problema de segurança que você encontre.</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="summary_debug_identifier">Identificador de depuração</string>
<string name="unique_id_to_clipboard">Identificador copiado para a área de transferência</string>
<string name="display_header_drawer_summary">Exibir um cabeçalho com o URL da instância do Selfoss na barra lateral.</string>
<string name="display_header_drawer_title">Cabeçalho da conta</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>
@ -120,19 +88,12 @@
<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>
<string name="add_to_favs_reader">Adicionar aos favoritos</string>
<string name="remove_to_favs_reader">Remover dos favoritos</string>
<string name="pref_content_reader_font_size">Tamanho da fonte do conteúdo do leitor de artigos</string>
<string name="pref_header_viewer">Visualizador de artigos</string>
<string name="refresh_dialog_message">Isso atualizará sua instância do Selfoss.</string>
<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="pref_acra_alwaysaccept">Envia relatórios de erros automaticamente</string>
<string name="pref_acra_alwaysaccept_enabled">Enviar relatórios de erro automaticamente</string>
<string name="pref_acra_alwaysaccept_disabled">Perguntar sempre, ao enviar relatórios de erro.</string>
<string name="pref_debug_crash_reports">Relatório de erro</string>
<string name="pref_debug_debug_logs">Log de depuração (Serão enviados sem uma caixa de diálogo)</string>
<string name="acra_login">Ativar registro de erros</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>
@ -141,7 +102,8 @@
<string name="pref_switch_items_caching">Save items for offline use</string>
<string name="pref_switch_update_sources">Check for new sources and tags</string>
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
<string name="no_network_connectivity">Not connected !</string>
<string name="network_connectivity_lost">"Network connection lost"</string>
<string name="network_connectivity_retrieved">"Network connection is now available"</string>
<string name="pref_switch_periodic_refresh">Sync articles</string>
<string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string>
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
@ -166,4 +128,9 @@
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string>
<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="drawer_error_loading_sources">Error loading sources…</string>
</resources>

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