Compare commits

..

112 Commits

Author SHA1 Message Date
8e9fd9c985 Changelog. 2017-11-14 19:38:18 +01:00
72400f71c0 Changed color of links in the article viewer. 2017-11-14 19:36:23 +01:00
1151587951 Merge branch 'master' of github.com:aminecmi/ReaderforSelfoss 2017-11-14 19:23:12 +01:00
abcd500045 Fixed #114. 2017-11-14 19:22:43 +01:00
beda24e736 New Crowdin translations (#112)
* New translations strings.xml (Portuguese)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Portuguese)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Portuguese, Brazilian)
2017-11-14 11:15:47 +01:00
37b2c5c2df Fixed toolbar and fab behavior #113. 2017-11-13 19:35:14 +01:00
7b5246ebf1 Update. 2017-11-13 18:58:57 +01:00
d6d5e72f48 New Crowdin translations (#111)
* New translations strings.xml (Portuguese)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Portuguese)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Portuguese, Brazilian)
2017-11-12 17:38:39 +01:00
fa8e88d489 Changelog. 2017-11-12 17:28:04 +01:00
1ef2da9f76 Fixing tests. 2017-11-12 17:22:40 +01:00
1ebd894be7 Tests fixes. 2017-11-12 17:14:21 +01:00
a9c493d105 Changelog. 2017-11-12 17:08:30 +01:00
f833d73fab Closes #109. 2017-11-12 17:05:47 +01:00
9e6602f114 Hiding FABs on scroll. 2017-11-12 15:41:28 +01:00
3bdfef9f8b Added changelog and changed fab size. 2017-11-12 07:55:01 +01:00
6f7f475a6b Enhancements. Three first items of #108. 2017-11-12 07:51:11 +01:00
8fc5fab67b Merge branch 'master' of github.com:aminecmi/ReaderforSelfoss 2017-11-11 21:01:48 +01:00
6927e92396 New Crowdin translations (#105)
* New translations strings.xml (French)

* New translations strings.xml (Afrikaans)

* New translations strings.xml (Japanese)

* New translations strings.xml (Ukrainian)

* New translations strings.xml (Turkish)

* New translations strings.xml (Swedish)

* New translations strings.xml (Spanish)

* New translations strings.xml (Serbian (Cyrillic))

* New translations strings.xml (Russian)

* New translations strings.xml (Romanian)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Polish)

* New translations strings.xml (Norwegian)

* New translations strings.xml (Korean)

* New translations strings.xml (Italian)

* New translations strings.xml (Arabic)

* New translations strings.xml (Hungarian)

* New translations strings.xml (Hebrew)

* New translations strings.xml (Greek)

* New translations strings.xml (German)

* New translations strings.xml (French)

* New translations strings.xml (Finnish)

* New translations strings.xml (Dutch)

* New translations strings.xml (Danish)

* New translations strings.xml (Czech)

* New translations strings.xml (Chinese Traditional)

* New translations strings.xml (Catalan)

* New translations strings.xml (Vietnamese)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Portuguese)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Afrikaans)

* New translations strings.xml (Japanese)

* New translations strings.xml (Ukrainian)

* New translations strings.xml (Turkish)

* New translations strings.xml (Swedish)

* New translations strings.xml (Spanish)

* New translations strings.xml (Serbian (Cyrillic))

* New translations strings.xml (Russian)

* New translations strings.xml (Romanian)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Arabic)

* New translations strings.xml (Greek)

* New translations strings.xml (Portuguese)

* New translations strings.xml (Polish)

* New translations strings.xml (Norwegian)

* New translations strings.xml (Korean)

* New translations strings.xml (Italian)

* New translations strings.xml (Hungarian)

* New translations strings.xml (Hebrew)

* New translations strings.xml (German)

* New translations strings.xml (Catalan)

* New translations strings.xml (French)

* New translations strings.xml (Finnish)

* New translations strings.xml (Dutch)

* New translations strings.xml (Danish)

* New translations strings.xml (Czech)

* New translations strings.xml (Chinese Traditional)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Vietnamese)
2017-11-11 21:01:25 +01:00
c7470396d7 Changelog. 2017-11-11 20:55:31 +01:00
f21570e2e4 New Crowdin translations (#104)
* New translations strings.xml (French)

* New translations strings.xml (Afrikaans)

* New translations strings.xml (Japanese)

* New translations strings.xml (Ukrainian)

* New translations strings.xml (Turkish)

* New translations strings.xml (Swedish)

* New translations strings.xml (Spanish)

* New translations strings.xml (Serbian (Cyrillic))

* New translations strings.xml (Russian)

* New translations strings.xml (Romanian)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Polish)

* New translations strings.xml (Norwegian)

* New translations strings.xml (Korean)

* New translations strings.xml (Italian)

* New translations strings.xml (Arabic)

* New translations strings.xml (Hungarian)

* New translations strings.xml (Hebrew)

* New translations strings.xml (Greek)

* New translations strings.xml (German)

* New translations strings.xml (French)

* New translations strings.xml (Finnish)

* New translations strings.xml (Dutch)

* New translations strings.xml (Danish)

* New translations strings.xml (Czech)

* New translations strings.xml (Chinese Traditional)

* New translations strings.xml (Catalan)

* New translations strings.xml (Vietnamese)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Portuguese)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Afrikaans)

* New translations strings.xml (Japanese)

* New translations strings.xml (Ukrainian)

* New translations strings.xml (Turkish)

* New translations strings.xml (Swedish)

* New translations strings.xml (Spanish)

* New translations strings.xml (Serbian (Cyrillic))

* New translations strings.xml (Russian)

* New translations strings.xml (Romanian)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Arabic)

* New translations strings.xml (Greek)

* New translations strings.xml (Portuguese)

* New translations strings.xml (Polish)

* New translations strings.xml (Norwegian)

* New translations strings.xml (Korean)

* New translations strings.xml (Italian)

* New translations strings.xml (Hungarian)

* New translations strings.xml (Hebrew)

* New translations strings.xml (German)

* New translations strings.xml (Catalan)

* New translations strings.xml (French)

* New translations strings.xml (Finnish)

* New translations strings.xml (Dutch)

* New translations strings.xml (Danish)

* New translations strings.xml (Czech)

* New translations strings.xml (Chinese Traditional)
2017-11-11 20:53:15 +01:00
51f406e20c New action bar. Closes #103. 2017-11-11 20:39:56 +01:00
9e3fde744e Changed to Textview to disaply the simple content from the API. 2017-11-11 16:50:17 +01:00
ccf406ae68 Update CONTRIBUTING.md 2017-11-11 14:47:40 +01:00
bc78d1e079 Changelog and updated target sdk. 2017-11-11 14:35:13 +01:00
d151eb261e Updates to make the build work. 2017-11-11 14:28:06 +01:00
0856598cd9 Changelog. 2017-11-11 08:44:18 +01:00
f0563efc62 Fixed #98. Using kotlin implementation for the drawer. Using the new style code. 2017-11-11 07:58:29 +01:00
84dfa9a8a5 New Crowdin translations (#100)
* New translations strings.xml (French)

* New translations strings.xml (Afrikaans)

* New translations strings.xml (Japanese)

* New translations strings.xml (Ukrainian)

* New translations strings.xml (Turkish)

* New translations strings.xml (Swedish)

* New translations strings.xml (Spanish)

* New translations strings.xml (Serbian (Cyrillic))

* New translations strings.xml (Russian)

* New translations strings.xml (Romanian)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Polish)

* New translations strings.xml (Norwegian)

* New translations strings.xml (Korean)

* New translations strings.xml (Italian)

* New translations strings.xml (Arabic)

* New translations strings.xml (Hungarian)

* New translations strings.xml (Hebrew)

* New translations strings.xml (Greek)

* New translations strings.xml (German)

* New translations strings.xml (French)

* New translations strings.xml (Finnish)

* New translations strings.xml (Dutch)

* New translations strings.xml (Danish)

* New translations strings.xml (Czech)

* New translations strings.xml (Chinese Traditional)

* New translations strings.xml (Catalan)

* New translations strings.xml (Vietnamese)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Portuguese)

* New translations strings.xml (Portuguese, Brazilian)
2017-11-08 11:36:41 +01:00
8e25489cca Update CHANGELOG.md 2017-11-08 11:24:48 +01:00
198f95e1ca Update CHANGELOG.md 2017-11-08 11:24:40 +01:00
7e02fe89ea New Crowdin translations (#97)
* New translations strings.xml (French)

* New translations strings.xml (Afrikaans)

* New translations strings.xml (Japanese)

* New translations strings.xml (Ukrainian)

* New translations strings.xml (Turkish)

* New translations strings.xml (Swedish)

* New translations strings.xml (Spanish)

* New translations strings.xml (Serbian (Cyrillic))

* New translations strings.xml (Russian)

* New translations strings.xml (Romanian)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Polish)

* New translations strings.xml (Norwegian)

* New translations strings.xml (Korean)

* New translations strings.xml (Italian)

* New translations strings.xml (Arabic)

* New translations strings.xml (Hungarian)

* New translations strings.xml (Hebrew)

* New translations strings.xml (Greek)

* New translations strings.xml (German)

* New translations strings.xml (French)

* New translations strings.xml (Finnish)

* New translations strings.xml (Dutch)

* New translations strings.xml (Danish)

* New translations strings.xml (Czech)

* New translations strings.xml (Chinese Traditional)

* New translations strings.xml (Catalan)

* New translations strings.xml (Vietnamese)
2017-11-04 11:57:30 +01:00
819356412c Optimizing imports. 2017-11-04 11:43:09 +01:00
deb789bc1b Some code cleaning. 2017-11-04 11:33:22 +01:00
133ba74548 Fixed changelog. 2017-11-04 10:59:13 +01:00
1461e32643 Update README.md 2017-11-02 10:11:19 +01:00
f400c3d9ac New translations strings.xml (French) (#96) 2017-11-02 09:42:22 +01:00
7e595a4f74 Reverted last commit 2017-11-02 09:39:46 +01:00
18c9c499b2 Delete crowdin.yml 2017-11-02 09:36:25 +01:00
24ae115ed4 New translations strings.xml (French) (#95) 2017-11-01 19:54:56 +01:00
7f345558cd Changelog. 2017-11-01 19:48:06 +01:00
57177cc910 Loading more on swipe. 2017-11-01 19:46:13 +01:00
cea258bc21 Fixes #45. User may need to reselect the theme. 2017-11-01 19:06:36 +01:00
ed9b1c8ba7 Only reporting the issue if there is an issue. 2017-11-01 18:13:54 +01:00
5a79fd89e9 Realy fixed the api call this time. 2017-10-29 21:11:03 +01:00
42a130db08 Changelog. 2017-10-29 20:18:35 +01:00
320a8d19de New translations strings.xml (French) (#94) 2017-10-29 20:14:52 +01:00
5721506007 Maybe fixed issue with marking items. 2017-10-29 20:10:02 +01:00
803e8cb2f4 Revert "This is causing bugs for Xperia XZ Premium device on pre-launch report."
This reverts commit 98492fd0c0.
2017-10-29 13:52:43 +01:00
98492fd0c0 This is causing bugs for Xperia XZ Premium device on pre-launch report. 2017-10-29 13:31:31 +01:00
0b07178577 Removed codebeat. 2017-10-29 12:57:54 +01:00
07e545079c Fixes full height cards problem. 2017-10-29 12:55:03 +01:00
95d64dc5e8 New Crowdin translations (#93)
* New translations strings.xml (Afrikaans)

* New translations strings.xml (Japanese)

* New translations strings.xml (Ukrainian)

* New translations strings.xml (Turkish)

* New translations strings.xml (Swedish)

* New translations strings.xml (Spanish)

* New translations strings.xml (Serbian (Cyrillic))

* New translations strings.xml (Russian)

* New translations strings.xml (Romanian)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Arabic)

* New translations strings.xml (Greek)

* New translations strings.xml (Polish)

* New translations strings.xml (Norwegian)

* New translations strings.xml (Korean)

* New translations strings.xml (Italian)

* New translations strings.xml (Hungarian)

* New translations strings.xml (Hebrew)

* New translations strings.xml (German)

* New translations strings.xml (Catalan)

* New translations strings.xml (French)

* New translations strings.xml (Finnish)

* New translations strings.xml (Dutch)

* New translations strings.xml (Danish)

* New translations strings.xml (Czech)

* New translations strings.xml (Chinese Traditional)

* New translations strings.xml (Vietnamese)
2017-10-27 19:56:04 +02:00
abe546dcda test fixes. 2017-10-27 19:13:57 +02:00
e6f367acaf Cleaning code to make them work ? 2017-10-27 18:51:30 +02:00
a9b61853b9 Updates and more. 2017-10-27 18:48:22 +02:00
5afc04a630 New Crowdin translations (#89)
* New translations strings.xml (Afrikaans)

* New translations strings.xml (Japanese)

* New translations strings.xml (Ukrainian)

* New translations strings.xml (Turkish)

* New translations strings.xml (Swedish)

* New translations strings.xml (Spanish)

* New translations strings.xml (Serbian (Cyrillic))

* New translations strings.xml (Russian)

* New translations strings.xml (Romanian)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Polish)

* New translations strings.xml (Norwegian)

* New translations strings.xml (Korean)

* New translations strings.xml (Italian)

* New translations strings.xml (Arabic)

* New translations strings.xml (Hungarian)

* New translations strings.xml (Hebrew)

* New translations strings.xml (Greek)

* New translations strings.xml (German)

* New translations strings.xml (French)

* New translations strings.xml (Finnish)

* New translations strings.xml (Dutch)

* New translations strings.xml (Danish)

* New translations strings.xml (Czech)

* New translations strings.xml (Chinese Traditional)

* New translations strings.xml (Catalan)

* New translations strings.xml (Vietnamese)
2017-10-23 18:47:16 +02:00
1da4cc2782 Fixes #86. 2017-10-23 18:29:44 +02:00
c5ebc89e4f New Crowdin translations (#85)
* New translations strings.xml (Catalan)

* New translations strings.xml (Vietnamese)
2017-10-23 15:23:14 +02:00
dfc1719cce New Crowdin translations (#84)
* New translations strings.xml (Afrikaans)

* New translations strings.xml (Japanese)

* New translations strings.xml (Ukrainian)

* New translations strings.xml (Turkish)

* New translations strings.xml (Swedish)

* New translations strings.xml (Spanish)

* New translations strings.xml (Serbian (Cyrillic))

* New translations strings.xml (Russian)

* New translations strings.xml (Romanian)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Polish)

* New translations strings.xml (Norwegian)

* New translations strings.xml (Korean)

* New translations strings.xml (Italian)

* New translations strings.xml (Arabic)

* New translations strings.xml (Hungarian)

* New translations strings.xml (Hebrew)

* New translations strings.xml (Greek)

* New translations strings.xml (German)

* New translations strings.xml (French)

* New translations strings.xml (Finnish)

* New translations strings.xml (Dutch)

* New translations strings.xml (Danish)

* New translations strings.xml (Czech)

* New translations strings.xml (Chinese Traditional)
2017-10-23 15:16:52 +02:00
0812259470 Temporary workaround for #83. 2017-10-23 15:09:23 +02:00
e1476c5840 Multiple fixes. 2017-10-23 14:31:04 +02:00
e30ea28e3f Fixed an issue with older versions of android. 2017-10-22 16:23:01 +02:00
4a6d3aab7f New translations from crowdin. 2017-10-21 21:53:53 +02:00
8157146498 Strings, updates, and sonarqube. 2017-10-21 20:11:18 +02:00
94d23888b1 Update CONTRIBUTING.md 2017-10-02 12:50:35 +02:00
737fe9bb4a Update Crowdin configuration file 2017-09-18 15:13:13 +02:00
0051ed2e73 Crowdin translation. 2017-09-16 22:28:20 +02:00
e0595957e2 Update README.md 2017-09-16 20:46:43 +02:00
8d09ff7fdb Added apk link to readme. 2017-09-10 19:25:12 +02:00
04feb66b07 Fixes #67. May need fine tuning. 2017-09-10 19:22:07 +02:00
54b2ac7f24 Fixes #10. Fixes #68. 2017-09-10 12:59:51 +02:00
12356a35fa Solved compilation warning. 2017-09-10 09:48:20 +02:00
12262304ac Trying to fix minification problem. 2017-09-10 09:19:28 +02:00
c58f97452e Fixed issues with secrets xml file. Removed it and remplaced with build config. 2017-09-06 21:17:01 +02:00
eb3872f7a6 Added a setting for displaying or hiding the account header. 2017-09-03 11:47:48 +02:00
9fa178d513 Closes #71 2017-09-02 17:55:02 +02:00
043b184065 Images are now loading on self signed certs. 2017-09-02 13:58:35 +02:00
10559bb894 Fixed build problem from last commit. 2017-09-02 12:51:11 +02:00
d0000d66b2 Fixes #70. Updated glide for images loading. 2017-09-02 10:02:35 +02:00
b447ac738a Fixes #62. 2017-08-31 20:00:58 +02:00
faebfc238c Closes #69. 2017-08-30 22:11:29 +02:00
c28fbd37cc Fixed issue with last commit. 2017-08-30 07:34:48 +02:00
4b8396959d Needed to add an exception for the log to work. 2017-08-30 07:22:28 +02:00
b39d510e07 Added reading article log. 2017-08-29 22:49:20 +02:00
286dda7f80 Update README.md 2017-08-27 19:33:21 +02:00
7bda896e2d Added the ability to choose the number of items loaded. 2017-08-26 21:36:19 +02:00
ba4feeea87 Trying to fix pre-lolipop svg drawable loading. 2017-08-16 22:15:33 +02:00
6f52eae3c6 Version updates. 2017-08-16 21:18:15 +02:00
40ea8d56e6 Closes #64 2017-08-16 20:18:49 +02:00
72e562e8a8 Reverted back to the old icon. 2017-08-16 07:51:28 +02:00
6fa01bfe19 Added changelog. 2017-08-04 21:44:29 +02:00
0ef59c9b91 Added a quick fix for accepting self signed certificates. 2017-08-04 21:42:35 +02:00
d768d2232b Added login optional debug logs. (#61) 2017-08-04 08:30:21 +02:00
b44a200731 A better icon. 2017-08-03 07:43:00 +02:00
016815e0d1 This test keep failing of firebase test lab. 2017-08-02 21:01:51 +02:00
590534e4a6 Splash screen fix, with some test problem. 2017-08-02 20:53:20 +02:00
7ea9d4e519 Changelog 2017-08-02 20:11:02 +02:00
e0ab09f533 Adaptive icon (#60)
* Removed mipmap folder from gitignore.

* New adaptive icon.

* Removed icon step from build.

* The icon seamed pixelated.
2017-08-02 20:08:31 +02:00
fbe98f1b16 Update CONTRIBUTING.md 2017-08-02 07:32:28 +02:00
d0675b8443 Update build.gradle 2017-07-30 19:01:05 +02:00
3ea1ed02ae Update build.gradle 2017-07-30 18:58:36 +02:00
ba120b1e0b Ignoring annoying bug with Samsung devices on 4.2.2 (#57)
* Updated gradle version.

* Fixed #55. Ignoring annoying non fatal bug on Samsung devices.
2017-07-30 18:37:44 +02:00
acf6995c2d Trying to handle app versionning from git tag. 2017-07-28 05:02:28 -04:00
8306860f90 Fixed #54 2017-07-28 07:33:21 +02:00
65974166be Update README.md 2017-07-27 22:03:57 +02:00
ee8924f986 Removed travis integration. 2017-07-27 22:02:14 +02:00
170e575465 Hiding the badge on reading all. 2017-07-27 22:02:14 +02:00
b7d5317b10 Fixed travis build. 2017-07-25 21:35:57 +02:00
f12e7748c5 Build v1.5.1.8 2017-07-25 21:17:34 +02:00
69a2418afc Fix 51 (#52)
* Some more bug fixes.

* Cleaning travis file.

* Fixes #51
2017-07-22 13:12:13 +02:00
4924ddd172 Changed CI from circle to travis. 2017-07-21 18:48:13 +02:00
1889b43786 Build and changelog. 2017-07-21 11:44:13 +02:00
149 changed files with 6707 additions and 1711 deletions

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 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. There are many ways to contribute to this project, you could 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/labels/help%20wanted) or [develop new things](https://github.com/aminecmi/ReaderforSelfoss/issues) 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)
### What I can't help you with. ### What I can't help you with.
@ -22,7 +22,8 @@ Always check if the web version of your instance is working.
### Bug reports/Feature request ### Bug reports/Feature request
* Always search before reporting an issue or asking for a feature to avoid duplicates. * Always search before reporting an issue or asking for a feature to avoid duplicates.
* Include every usefull details (app version, phone model, Android version and screenshots when possible) * Include your unique user id. It's displayed on the debug settings page. (You can tap it, it'll be copied to your clipboard)
* Include every other useful details (app version, phone model, Android version and screenshots when possible).
* Avoid bumping non-fatal issues, or feature requests. I'll try to fix them as soon as possible, and try to prioritize the requests. (You may wan to use the [reactions](https://github.com/blog/2119-add-reactions-to-pull-requests-issues-and-comments) for that) * Avoid bumping non-fatal issues, or feature requests. I'll try to fix them as soon as possible, and try to prioritize the requests. (You may wan to use the [reactions](https://github.com/blog/2119-add-reactions-to-pull-requests-issues-and-comments) for that)
### Pull requests ### Pull requests
@ -30,7 +31,7 @@ Always check if the web version of your instance is working.
* Please ask before starting to work on an issue. I may be working on it, or someone else could be doing so. * 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. * 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. * Your code must be simple and clear enough to avoid using comments to explain what it does.
* Follow the used coding style [the official one](https://kotlinlang.org/docs/reference/coding-conventions.html) ([some idoms for reference](http://kotlinlang.org/docs/reference/idioms.html)) with more to come. * Follow the used coding style [the android koding style](https://android.github.io/kotlin-guides/style.html) ([some idoms for reference](http://kotlinlang.org/docs/reference/idioms.html)) with more to come.
* Try as much as possible to write a test for your feature, and if you do so, run it, and make it work. * Try as much as possible to write a test for your feature, and if you do so, run it, and make it work.
* Always check your changes and discard the ones that are irrelevant to your feature or bugfix. * Always check your changes and discard the ones that are irrelevant to your feature or bugfix.
* Have meaningful commit messages. * Have meaningful commit messages.
@ -38,18 +39,40 @@ Always check if the web version of your instance is working.
* Be willing to accept criticism on your PRs (as I am on mine). * Be willing to accept criticism on your PRs (as I am on mine).
* Remember that PR review can take time. * Remember that PR review can take time.
# Build the project # Build the project
You can directly import this project into IntellIJ/Android Studio. You can directly import this project into IntellIJ/Android Studio.
You'll have to: You'll have to:
- [Create your own launcher icon](https://developer.android.com/studio/write/image-asset-studio.html#creating-launcher) - Configure fabric and add your `apiKey` and `apiSecret` in the `fabric.properties` file.
- Create a firebase project and add the `google-services.json` to the `app/` folder.
- Define some parameters either in `~/.gradle/gradle.properties` or as gradle parameters (see the examples)
- Configure Fabric, or [remove it](https://docs.fabric.io/android/fabric/settings/removing.html#). - mercuryApiKey: A [Mercury](https://mercury.postlight.com/web-parser/) web parser api key for the internal browser
- Define the following in `res/values/strings.xml` or create `res/values/secrets.xml` - feedbackEmail: An email to receive users feedback.
- sourceUrl: an url to the source code, used in the settings. **It can be empty.**
- trackerUrl: an url to the tracker, used in the settings. **It can be empty.**
- githubToken: a github token used to report issues from within the app. [Details here](https://github.com/heinrichreimer/android-issue-reporter#how-to-create-a-bot-key). **It can be empty.**
- appLoginUrl, appLoginUsername and appLoginPassword: url, username and password of a selfoss instance. **These are only used for tests. They can be empty if you don't test API calls.**
- mercury: A [Mercury](https://mercury.postlight.com/web-parser/) web parser api key for the internal browser ### Examples:
- feedback_email: An email to receive users feedback. #### Inside ~/.gradle/gradle.properties
- source_url: an url to the source code, used in the settings
- tracker_url: an url to the tracker, used in the settings ```
appLoginUrl="URL" # It can be empty.
appLoginUsername="LOGIN" # It can be empty.
appLoginPassword="PASS" # It can be empty.
mercuryApiKey="LONGAPIKEY"
feedbackEmail="EMAIL"
sourceUrl="URLSOURCE" # It can be empty.
trackerUrl="URLTRACKER" # It can be empty.
githubToken="GITHUBTOKEN" # It can be empty or use https://github.com/heinrichreimer/android-issue-reporter#how-to-create-a-bot-key to generate one
```
#### As gradle parameters
```
./gradlew .... -P appLoginUrl="URL" -P appLoginUsername="LOGIN" -P appLoginPassword="PASS" -P mercuryApiKey="LONGAPIKEY" -P feedbackEmail="EMAIL" -P sourceUrl="URLSOURCE" -P trackerUrl="URLTRACKER" -P githubToken="GITHUBTOKEN"
```

5
.gitignore vendored
View File

@ -214,7 +214,6 @@ gradle-app.setting
# End of https://www.gitignore.io/api/java,gradle,android,androidstudio # End of https://www.gitignore.io/api/java,gradle,android,androidstudio
secrets.xml release/
mipmap-* crowdin.properties
release/

View File

@ -1,3 +1,196 @@
**1.5.4.12**
- Fixed fab and toolbar issue (#113)
- Fixed links clickable (#114)
- Changed the link colors in the article viewer
**1.5.4.11**
- Hiding FABs on scroll.
- Closing #109 (code cleaning)
- Hiding fabs on scroll (#101)
**1.5.4.10**
- Displaying a loader when "reading more" in the article viewer.
- Displaying the thumbnail instead of icon on the article viewer.
- Scrolling to top when loading content with the "read more" button.
**1.5.4.09**
- Using the kotlin wrapper for the material drawer (see #98 for more details).
- Updated support libraries
- Changed the Floating Action Button to the support library version.
- New reader activity action bar #103.
**1.5.4.08**
- Thanks @jrafaelsantana for translating the whole app in Brazilian Portuguese.
**1.5.4.07**
- Loading more items on swipe too.
- Fixed popup menu style. User may need to reselect the theme.
- Disabled reporting marking items as read if there isn't an issue.
**1.5.4.05/06**
- Translation fix.
**1.5.4.04**
- Fixing an issue with marking items as read (something related to an old version of selfoss).
**1.5.4.03**
- Trying to fix some issue with pre-launch reports. Reverted because it seems to be related to the dev console side.
**1.5.4.02**
- Fixing full height cards issue.
**1.5.4.01**
- Removed the "apk downloaded from outside of playstore" message.
- Versions update.
- HTML viewer version update. It should fix an issue with images.
- Some code cleaning.
**1.5.4.00**
- Added issue reporting from within the app.
**1.5.3.06**
- Fixed infinite scroll not working.
- Fixed logs not working.
- Temporary workaround handling opening invalid urls. Waiting to solve #83.
**1.5.3.05**
- Fixed an issue on older versions of Android.
- Libs update.
**1.5.3.04**
- Crowdin translations
**1.5.3.03**
- Libs updates.
- Translation fix.
**1.5.3.01/02**
- Added translation link to the settings page.
- Added the translation link to the README.
**1.5.3.00**
- (BETA) Added pull from bottom to load more pages of results. May be buggy.
**1.5.2.18/19**
- APK minification finally working. That means less space taken !
- Added an option to log every API call.
**1.5.2.17**
- Source code and tracker links weren't being set, and updated the contributing doc.
**1.5.2.15/16**
- Adding an account header on the lateral drawer.
- The account header is only displayed when the setting is enabled.
**1.5.2.13/14**
- Updated glide.
- Loading images from self signed certificate now working.
**1.5.2.12**
- Self signed certificates are now working for loading data. Image are not loading yet.
**1.5.2.11**
- Added a random unique identifier to be used in the logs.
**1.5.2.08/09/10**
- Added settable logs for reading articles problems.
**1.5.2.07**
- Added the ability to choose the number of items loaded (the maximum value is 200 and is imposed by the selfoss api)
**1.5.2.06**
- Fix problem introduced in 1.5.2.04. SVG file not working on older versions of android.
**1.5.2.05**
- Versions updates
**1.5.2.04**
- Reverted to the old icon.
- Better icon for the intro activity.
- Updated gradle version.
**1.5.2.03**
- Added the ability to accept self signed certificates. (Needs more testing)
**1.5.2.02**
- Added optional login option.
**1.5.2.01**
- New (Better) Icon !
**1.5.2.0**
- New Icon !
**1.5.1.9/10/11**
- Hiding the unread badge when marking all items as read.
**1.5.1.8**
- Fixes and libs updates.
**1.5.1.7**
- Bug fixes.
- Code cleaning
**1.5.1.6** **1.5.1.6**
- Added back the badges after it was fixed on the library side. - Added back the badges after it was fixed on the library side.
@ -196,4 +389,4 @@ _Updates_
**1.3.3.4** **1.3.3.4**
... ...

View File

@ -1,10 +1,8 @@
# ReaderForSelfoss # ReaderForSelfoss
[![Gitter chat](https://badges.gitter.im/amine-bou/ReaderForSelfoss.png)](https://gitter.im/amine-bou/ReaderForSelfoss) [![Crowdin](https://d322cqt584bo4o.cloudfront.net/readerforselfoss/localized.svg)](https://crowdin.com/project/readerforselfoss) [![Gitter chat](https://badges.gitter.im/amine-bou/ReaderForSelfoss.png)](https://gitter.im/amine-bou/ReaderForSelfoss)
[![CircleCI](https://circleci.com/gh/aminecmi/ReaderforSelfoss/tree/master.svg?style=svg)](https://circleci.com/gh/aminecmi/ReaderforSelfoss/tree/master) [![Build Status](http://jenkins.amine-bou.fr/job/ReaderForSelfoss/badge/icon)](http://jenkins.amine-bou.fr/job/ReaderForSelfoss/)
[![codebeat badge](https://codebeat.co/badges/bce66c0f-fd28-4341-a159-3b6dd22ac854)](https://codebeat.co/projects/github-com-aminecmi-readerforselfoss-master)
[![Code Triagers Badge](https://www.codetriage.com/aminecmi/readerforselfoss/badges/users.svg)](https://www.codetriage.com/aminecmi/readerforselfoss) [![Code Triagers Badge](https://www.codetriage.com/aminecmi/readerforselfoss/badges/users.svg)](https://www.codetriage.com/aminecmi/readerforselfoss)
@ -12,6 +10,8 @@ This is the repo of [Reader For Selfoss](https://play.google.com/store/apps/deta
It's an RSS Reader for Android, that **only** works with [Selfoss](https://selfoss.aditu.de/) It's an RSS Reader for Android, that **only** works with [Selfoss](https://selfoss.aditu.de/)
The last APK built from source is available [here](https://jenkins.amine-bou.fr/job/ReaderForSelfoss/lastSuccessfulBuild/artifact/SignApksBuilder-out/selfoss-key/selfoss/app-githubConfig-release-unsigned.apk/app-githubConfig-release.apk).
## Want to help ? ## Want to help ?
@ -22,3 +22,5 @@ Check the [Contribution guide](https://github.com/aminecmi/ReaderforSelfoss/blob
- [Check what changed](https://github.com/aminecmi/ReaderforSelfoss/blob/master/CHANGELOG.md) - [Check what changed](https://github.com/aminecmi/ReaderforSelfoss/blob/master/CHANGELOG.md)
- [See what I'm doing](https://github.com/aminecmi/ReaderforSelfoss/projects/1) - [See what I'm doing](https://github.com/aminecmi/ReaderforSelfoss/projects/1)
- [Create an issue, or request a new feature](https://github.com/aminecmi/ReaderforSelfoss/issues) - [Create an issue, or request a new feature](https://github.com/aminecmi/ReaderforSelfoss/issues)
- [Help translation the app](https://crowdin.com/project/readerforselfoss)
- [Ask for help](https://gitter.im/amine-bou/ReaderForSelfoss)

View File

@ -8,25 +8,46 @@ buildscript {
} }
} }
def gitVersion() {
def process = "git describe --abbrev=0 --tags".execute()
return process.text.substring(1).replaceAll("\\.", "")
}
def versionCodeFromGit() {
println "version code " + gitVersion().toInteger()
return gitVersion().toInteger()
}
def versionNameFromGit() {
println "version code " + gitVersion().trim()
return gitVersion().trim()
}
apply plugin: 'org.sonarqube'
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'io.fabric' apply plugin: 'io.fabric'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
repositories { repositories {
maven { url 'https://maven.fabric.io/public' } maven {
url 'https://maven.fabric.io/public'
}
} }
android { android {
compileSdkVersion 26 compileSdkVersion 27
buildToolsVersion "26.0.0" buildToolsVersion '27.0.0'
defaultConfig { defaultConfig {
applicationId "apps.amine.bou.readerforselfoss" applicationId "apps.amine.bou.readerforselfoss"
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 25 targetSdkVersion 27
versionCode 1516 versionCode versionCodeFromGit()
versionName "1.5.1.6" versionName versionNameFromGit()
// Enabling multidex support. // Enabling multidex support.
multiDexEnabled true multiDexEnabled true
@ -38,10 +59,18 @@ android {
// tests // tests
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
buildConfigField "String", "MERCURY_KEY", mercuryApiKey
buildConfigField "String", "FEEDBACK_EMAIL", feedbackEmail
buildConfigField "String", "SOURCE_URL", sourceUrl
buildConfigField "String", "TRACKER_URL", trackerUrl
buildConfigField "String", "TRANSLATION_URL", translationUrl
buildConfigField "String", "GITHUB_TOKEN", githubToken
} }
buildTypes { buildTypes {
release { release {
minifyEnabled false minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro' 'proguard-rules.pro'
} }
@ -68,77 +97,79 @@ android {
dependencies { dependencies {
// Testing // Testing
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2' androidTestCompile 'com.android.support.test.espresso:espresso-core:3.0.1'
androidTestCompile 'com.android.support.test:runner:0.5' androidTestCompile 'com.android.support.test:runner:1.0.1'
// Espresso-contrib for DatePicker, RecyclerView, Drawer actions, Accessibility checks, CountingIdlingResource // Espresso-contrib for DatePicker, RecyclerView, Drawer actions, Accessibility checks, CountingIdlingResource
androidTestCompile 'com.android.support.test.espresso:espresso-contrib:2.2.2' androidTestCompile 'com.android.support.test.espresso:espresso-contrib:3.0.1'
// Espresso-intents for validation and stubbing of Intents // Espresso-intents for validation and stubbing of Intents
androidTestCompile 'com.android.support.test.espresso:espresso-intents:2.2.2' androidTestCompile 'com.android.support.test.espresso:espresso-intents:3.0.1'
compile fileTree(dir: 'libs', include: ['*.jar']) compile fileTree(dir: 'libs', include: ['*.jar'])
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
// Android Support // Android Support
compile 'com.android.support:appcompat-v7:26.0.0-beta2' compile 'com.android.support:appcompat-v7:27.0.0'
compile 'com.android.support:design:26.0.0-beta2' compile 'com.android.support:design:27.0.0'
compile 'com.android.support:recyclerview-v7:26.0.0-beta2' compile 'com.android.support:recyclerview-v7:27.0.0'
compile 'com.android.support:support-v4:26.0.0-beta2' compile 'com.android.support:support-v4:27.0.0'
compile 'com.android.support:support-vector-drawable:26.0.0-beta2' compile 'com.android.support:support-vector-drawable:27.0.0'
compile 'com.android.support:customtabs:26.0.0-beta2' compile 'com.android.support:customtabs:27.0.0'
compile 'com.android.support:cardview-v7:26.0.0-beta2' compile 'com.android.support:cardview-v7:27.0.0'
compile 'com.android.support.constraint:constraint-layout:1.0.2' compile 'com.android.support.constraint:constraint-layout:1.0.2'
// Firebase + crashlytics // Firebase + crashlytics
compile 'com.google.firebase:firebase-core:11.0.2' compile 'com.google.firebase:firebase-core:11.4.2'
compile 'com.google.firebase:firebase-config:11.0.2' compile 'com.google.firebase:firebase-config:11.4.2'
compile 'com.google.firebase:firebase-invites:11.0.2' compile 'com.google.firebase:firebase-invites:11.4.2'
compile('com.crashlytics.sdk.android:crashlytics:2.6.8@aar') { compile('com.crashlytics.sdk.android:crashlytics:2.7.1@aar') {
transitive = true transitive = true;
} }
//multidex //multidex
compile 'com.android.support:multidex:1.0.1' compile 'com.android.support:multidex:1.0.2'
// Intro // Intro
compile 'agency.tango.android:material-intro-screen:0.0.5' compile 'agency.tango.android:material-intro-screen:0.0.5'
// About // About
compile('com.mikepenz:aboutlibraries:5.9.6@aar') { compile('com.mikepenz:aboutlibraries:6.0.0@aar') {
transitive = true transitive = true
} }
// Retrofit + http logging + okhttp // Retrofit + http logging + okhttp
compile 'com.squareup.retrofit2:retrofit:2.3.0' compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.okhttp3:logging-interceptor:3.8.0' compile 'com.squareup.okhttp3:logging-interceptor:3.9.0'
compile 'com.squareup.retrofit2:converter-gson:2.3.0' compile 'com.squareup.retrofit2:converter-gson:2.3.0'
compile 'com.burgstaller:okhttp-digest:1.12' compile 'com.burgstaller:okhttp-digest:1.12'
// Material-ish things // Material-ish things
compile 'com.ashokvarma.android:bottom-navigation-bar:2.0.2' compile 'com.ashokvarma.android:bottom-navigation-bar:2.0.3'
compile 'com.melnykov:floatingactionbutton:1.3.0'
compile 'com.github.jd-alexander:LikeButton:0.2.1' compile 'com.github.jd-alexander:LikeButton:0.2.1'
compile 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1' compile 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
compile 'org.sufficientlysecure:html-textview:3.3' compile 'org.sufficientlysecure:html-textview:3.5'
// glide // glide
compile 'com.github.bumptech.glide:glide:3.7.0' compile 'com.github.bumptech.glide:glide:4.1.1'
compile 'com.github.bumptech.glide:okhttp3-integration:4.1.1'
// Asking politely users to rate the app // Asking politely users to rate the app
compile 'com.github.stkent:amplify:1.5.0' compile 'com.github.stkent:amplify:2.1.0'
// For the article reader // For the article reader
compile 'com.klinkerapps:drag-dismiss-activity:1.4.2' compile 'com.klinkerapps:drag-dismiss-activity:1.5.0'
// Drawer // Drawer
compile('com.mikepenz:materialdrawer:5.9.3@aar') { implementation 'co.zsmb:materialdrawer-kt:1.2.1'
transitive = true
}
compile 'com.anupcowkur:reservoir:3.1.0' compile 'com.anupcowkur:reservoir:3.1.0'
// Themes // Themes
compile 'com.52inc:scoops:1.0.0' compile 'com.52inc:scoops:1.0.0'
// Github issues reporter
compile 'com.heinrichreimersoftware:android-issue-reporter:1.3.1'
compile 'com.github.rubensousa:floatingtoolbar:1.5.1'
} }
apply plugin: 'com.google.gms.google-services' apply plugin: 'com.google.gms.google-services'
@ -147,6 +178,7 @@ apply plugin: 'com.google.gms.google-services'
afterEvaluate { afterEvaluate {
initFabricPropertiesIfNeeded() initFabricPropertiesIfNeeded()
initAppLoginPropertiesIfNeeded() initAppLoginPropertiesIfNeeded()
initAppForSecretPropertiesIfNeeded()
} }
def initFabricPropertiesIfNeeded() { def initFabricPropertiesIfNeeded() {
@ -170,4 +202,19 @@ def initAppLoginPropertiesIfNeeded() {
entry(key: "appLoginPassword", value: System.getProperty("appLoginPassword")) entry(key: "appLoginPassword", value: System.getProperty("appLoginPassword"))
} }
} }
} }
def initAppForSecretPropertiesIfNeeded() {
def propertiesFile = file(System.getProperty("user.home") + '/.gradle/gradle.properties')
if (!propertiesFile.exists()) {
def commentMessage = "This is autogenerated local property from system environment to prevent key to be committed to source control."
ant.propertyfile(file: System.getProperty("user.home") + "/.gradle/gradle.properties", comment: commentMessage) {
entry(key: "mercuryApiKey", value: System.getProperty("mercuryApiKey"))
entry(key: "feedbackEmail", value: System.getProperty("feedbackEmail"))
entry(key: "sourceUrl", value: System.getProperty("sourceUrl"))
entry(key: "trackerUrl", value: System.getProperty("trackerUrl"))
entry(key: "translationUrl", value: System.getProperty("translationUrl"))
entry(key: "githubToken", value: System.getProperty("githubToken"))
}
}
}

View File

@ -56,4 +56,17 @@
#Bottom bar lib #Bottom bar lib
-dontwarn com.roughike.bottombar.** -dontwarn com.roughike.bottombar.**
# self signed glidemodule
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public class * extends com.bumptech.glide.AppGlideModule
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
**[] $VALUES;
public *;
}
-dontwarn com.anupcowkur.reservoir.**
-dontwarn javax.annotation.**

View File

@ -23,8 +23,8 @@ import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import apps.amine.bou.readerforselfoss.settings.SettingsActivity
import apps.amine.bou.readerforselfoss.utils.Config import apps.amine.bou.readerforselfoss.utils.Config
import com.heinrichreimersoftware.androidissuereporter.IssueReporterLauncher
import org.junit.After import org.junit.After
@ -72,9 +72,6 @@ class HomeActivityEspressoTest {
onView(withContentDescription(R.string.abc_toolbar_collapse_description)).perform(click()) onView(withContentDescription(R.string.abc_toolbar_collapse_description)).perform(click())
onView(withMenu(id = R.id.readAll, titleId = R.string.readAll)).perform(click())
openActionBarOverflowOrOptionsMenu(context) openActionBarOverflowOrOptionsMenu(context)
onView(withMenu(id = R.id.refresh, titleId = R.string.menu_home_refresh)) onView(withMenu(id = R.id.refresh, titleId = R.string.menu_home_refresh))
@ -86,9 +83,6 @@ class HomeActivityEspressoTest {
intended(hasComponent(LoginActivity::class.java.name), times(1)) intended(hasComponent(LoginActivity::class.java.name), times(1))
onView(isRoot()).perform(pressBack())
} }
@Test @Test
@ -98,8 +92,9 @@ class HomeActivityEspressoTest {
onView(withId(R.id.material_drawer_layout)).perform(DrawerActions.open()) onView(withId(R.id.material_drawer_layout)).perform(DrawerActions.open())
onView(withText(R.string.action_about)).perform(click()) onView(withText(R.string.drawer_report_bug)).perform(click())
intended(hasComponent(LibsActivity::class.java.name)) intended(hasComponent(IssueReporterLauncher.Activity::class.java.name))
onView(isRoot()).perform(pressBack())
onView(isRoot()).perform(pressBack()) onView(isRoot()).perform(pressBack())
intended(hasComponent(HomeActivity::class.java.name)) intended(hasComponent(HomeActivity::class.java.name))
@ -108,10 +103,6 @@ class HomeActivityEspressoTest {
onView(withId(R.id.material_drawer_layout)).perform(DrawerActions.open()) onView(withId(R.id.material_drawer_layout)).perform(DrawerActions.open())
onView(withText(R.string.drawer_action_clear)).perform(click()) onView(withText(R.string.drawer_action_clear)).perform(click())
// bug
//onView(withText(R.string.title_activity_settings)).perform(scrollTo(), click())
//intended(hasComponent(SettingsActivity::class.java.name))
} }
// TODO: test articles opening and actions for cards and lists // TODO: test articles opening and actions for cards and lists

View File

@ -91,29 +91,6 @@ class IntroActivityEspressoTest {
} }
@Test
fun clickSelfossUrl() {
rule.launchActivity(Intent())
onView(withText(R.string.intro_hello_title)).check(matches(isDisplayed()))
onView(withId(R.id.button_next)).perform(click())
onView(withId(R.id.button_message)).perform(click())
intended(
allOf(
hasData(
hasHost(
equalTo("selfoss.aditu.de")
)
),
hasAction(Intent.ACTION_VIEW)
)
)
}
@After @After
fun releaseIntents() { fun releaseIntents() {
Intents.release() Intents.release()

View File

@ -32,18 +32,18 @@ class LoginActivityEspressoTest {
@Rule @JvmField @Rule @JvmField
val rule = ActivityTestRule(LoginActivity::class.java, true, false) val rule = ActivityTestRule(LoginActivity::class.java, true, false)
lateinit var context: Context private lateinit var context: Context
lateinit var url: String private lateinit var url: String
lateinit var username: String private lateinit var username: String
lateinit var password: String private lateinit var password: String
@Before @Before
fun setUp() { fun setUp() {
context = InstrumentationRegistry.getInstrumentation().targetContext context = InstrumentationRegistry.getInstrumentation().targetContext
val editor = val editor =
context context
.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE) .getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
.edit() .edit()
editor.clear() editor.clear()
editor.commit() editor.commit()
@ -77,12 +77,12 @@ class LoginActivityEspressoTest {
fun wrongLoginUrl() { fun wrongLoginUrl() {
rule.launchActivity(Intent()) rule.launchActivity(Intent())
onView(withId(R.id.login_progress)) onView(withId(R.id.loginProgress))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE))) .check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)))
onView(withId(R.id.url)).perform(click()).perform(typeText("WRONGURL")) onView(withId(R.id.urlView)).perform(click()).perform(typeText("WRONGURL"))
onView(withId(R.id.email_sign_in_button)).perform(click()) onView(withId(R.id.signInButton)).perform(click())
onView(withId(R.id.urlLayout)).check(matches(isHintOrErrorEnabled())) onView(withId(R.id.urlLayout)).check(matches(isHintOrErrorEnabled()))
} }
@ -94,24 +94,24 @@ class LoginActivityEspressoTest {
rule.launchActivity(Intent()) rule.launchActivity(Intent())
onView(withId(R.id.url)).perform(click()).perform(typeText(url), closeSoftKeyboard()) onView(withId(R.id.urlView)).perform(click()).perform(typeText(url), closeSoftKeyboard())
onView(withId(R.id.withLogin)).perform(click()) onView(withId(R.id.withLogin)).perform(click())
onView(withId(R.id.email_sign_in_button)).perform(click()) onView(withId(R.id.signInButton)).perform(click())
onView(withId(R.id.loginLayout)).check(matches(isHintOrErrorEnabled())) onView(withId(R.id.loginLayout)).check(matches(isHintOrErrorEnabled()))
onView(withId(R.id.passwordLayout)).check(matches(isHintOrErrorEnabled())) onView(withId(R.id.passwordLayout)).check(matches(isHintOrErrorEnabled()))
onView(withId(R.id.login)).perform(click()).perform(typeText(username), closeSoftKeyboard()) onView(withId(R.id.loginView)).perform(click()).perform(typeText(username), closeSoftKeyboard())
onView(withId(R.id.passwordLayout)).check(matches(isHintOrErrorEnabled())) onView(withId(R.id.passwordLayout)).check(matches(isHintOrErrorEnabled()))
onView(withId(R.id.email_sign_in_button)).perform(click()) onView(withId(R.id.signInButton)).perform(click())
onView(withId(R.id.passwordLayout)).check( onView(withId(R.id.passwordLayout)).check(
matches( matches(
isHintOrErrorEnabled()) isHintOrErrorEnabled())
) )
} }
@ -121,15 +121,15 @@ class LoginActivityEspressoTest {
rule.launchActivity(Intent()) rule.launchActivity(Intent())
onView(withId(R.id.url)).perform(click()).perform(typeText(url), closeSoftKeyboard()) onView(withId(R.id.urlView)).perform(click()).perform(typeText(url), closeSoftKeyboard())
onView(withId(R.id.withLogin)).perform(click()) onView(withId(R.id.withLogin)).perform(click())
onView(withId(R.id.login)).perform(click()).perform(typeText(username), closeSoftKeyboard()) onView(withId(R.id.loginView)).perform(click()).perform(typeText(username), closeSoftKeyboard())
onView(withId(R.id.password)).perform(click()).perform(typeText("WRONGPASS"), closeSoftKeyboard()) onView(withId(R.id.passwordView)).perform(click()).perform(typeText("WRONGPASS"), closeSoftKeyboard())
onView(withId(R.id.email_sign_in_button)).perform(click()) onView(withId(R.id.signInButton)).perform(click())
onView(withId(R.id.urlLayout)).check(matches(isHintOrErrorEnabled())) onView(withId(R.id.urlLayout)).check(matches(isHintOrErrorEnabled()))
onView(withId(R.id.loginLayout)).check(matches(isHintOrErrorEnabled())) onView(withId(R.id.loginLayout)).check(matches(isHintOrErrorEnabled()))
@ -142,15 +142,15 @@ class LoginActivityEspressoTest {
rule.launchActivity(Intent()) rule.launchActivity(Intent())
onView(withId(R.id.url)).perform(click()).perform(typeText(url), closeSoftKeyboard()) onView(withId(R.id.urlView)).perform(click()).perform(typeText(url), closeSoftKeyboard())
onView(withId(R.id.withLogin)).perform(click()) onView(withId(R.id.withLogin)).perform(click())
onView(withId(R.id.login)).perform(click()).perform(typeText(username), closeSoftKeyboard()) onView(withId(R.id.loginView)).perform(click()).perform(typeText(username), closeSoftKeyboard())
onView(withId(R.id.password)).perform(click()).perform(typeText(password), closeSoftKeyboard()) onView(withId(R.id.passwordView)).perform(click()).perform(typeText(password), closeSoftKeyboard())
onView(withId(R.id.email_sign_in_button)).perform(click()) onView(withId(R.id.signInButton)).perform(click())
intended(hasComponent(HomeActivity::class.java.name)) intended(hasComponent(HomeActivity::class.java.name))

View File

@ -28,7 +28,8 @@
android:name=".IntroActivity" android:name=".IntroActivity"
android:theme="@style/Theme.Intro"> android:theme="@style/Theme.Intro">
</activity> </activity>
<activity android:name=".LoginActivity" <activity
android:name=".LoginActivity"
android:label="@string/title_activity_login"> android:label="@string/title_activity_login">
</activity> </activity>
<activity android:name=".HomeActivity"> <activity android:name=".HomeActivity">
@ -41,13 +42,15 @@
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value="apps.amine.bou.readerforselfoss.HomeActivity" /> android:value="apps.amine.bou.readerforselfoss.HomeActivity" />
</activity> </activity>
<activity android:name=".SourcesActivity" <activity
android:name=".SourcesActivity"
android:parentActivityName=".HomeActivity"> android:parentActivityName=".HomeActivity">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value=".HomeActivity" /> android:value=".HomeActivity" />
</activity> </activity>
<activity android:name=".AddSourceActivity" <activity
android:name=".AddSourceActivity"
android:parentActivityName=".SourcesActivity"> android:parentActivityName=".SourcesActivity">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
@ -61,9 +64,13 @@
<data android:mimeType="text/plain" /> <data android:mimeType="text/plain" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name=".ReaderActivity" <activity
android:theme="@style/DragDismissTheme"> android:name=".ReaderActivity">
</activity> </activity>
<meta-data
android:name="apps.amine.bou.readerforselfoss.utils.glide.SelfSignedGlideModule"
android:value="GlideModule" />
</application> </application>
</manifest> </manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -2,22 +2,27 @@ package apps.amine.bou.readerforselfoss
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.preference.PreferenceManager
import android.support.constraint.ConstraintLayout import android.support.constraint.ConstraintLayout
import android.support.v7.app.AppCompatActivity import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
import android.view.View import android.view.View
import android.widget.* import android.widget.AdapterView
import android.widget.ArrayAdapter
import retrofit2.Call import android.widget.EditText
import retrofit2.Callback import android.widget.ProgressBar
import retrofit2.Response import android.widget.Spinner
import android.widget.TextView
import android.widget.Toast
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
import apps.amine.bou.readerforselfoss.api.selfoss.Spout import apps.amine.bou.readerforselfoss.api.selfoss.Spout
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
import apps.amine.bou.readerforselfoss.utils.Config import apps.amine.bou.readerforselfoss.utils.Config
import apps.amine.bou.readerforselfoss.utils.isUrlValid import apps.amine.bou.readerforselfoss.utils.isBaseUrlValid
import com.ftinc.scoop.Scoop import com.ftinc.scoop.Scoop
import kotlinx.android.synthetic.main.activity_add_source.*
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class AddSourceActivity : AppCompatActivity() { class AddSourceActivity : AppCompatActivity() {
@ -28,39 +33,43 @@ class AddSourceActivity : AppCompatActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
Scoop.getInstance().apply(this) Scoop.getInstance().apply(this)
setContentView(R.layout.activity_add_source) setContentView(R.layout.activity_add_source)
val toolbar: Toolbar = findViewById(R.id.toolbar)
setSupportActionBar(toolbar) setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true) supportActionBar?.setDisplayShowHomeEnabled(true)
val mProgress: ProgressBar = findViewById(R.id.progress)
val mForm: ConstraintLayout = findViewById(R.id.formContainer)
val mNameInput: EditText = findViewById(R.id.nameInput)
val mSourceUri: EditText = findViewById(R.id.sourceUri)
val mTags: EditText = findViewById(R.id.tags)
val mSpoutsSpinner: Spinner = findViewById(R.id.spoutsSpinner)
val mSaveBtn: Button = findViewById(R.id.saveBtn)
var api: SelfossApi? = null var api: SelfossApi? = null
try { try {
api = SelfossApi(this, this@AddSourceActivity) val prefs = PreferenceManager.getDefaultSharedPreferences(this)
api = SelfossApi(
this,
this@AddSourceActivity,
prefs.getBoolean("isSelfSignedCert", false),
prefs.getBoolean("should_log_everything", false)
)
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
mustLoginToAddSource() mustLoginToAddSource()
} }
val intent = intent maybeGetDetailsFromIntentSharing(intent, sourceUri, nameInput)
if (Intent.ACTION_SEND == intent.action && "text/plain" == intent.type) {
mSourceUri.setText(intent.getStringExtra(Intent.EXTRA_TEXT)) saveBtn.setOnClickListener {
mNameInput.setText(intent.getStringExtra(Intent.EXTRA_TITLE)) handleSaveSource(tags, nameInput.text.toString(), sourceUri.text.toString(), api!!)
} }
mSaveBtn.setOnClickListener { val config = Config(this)
handleSaveSource(mTags, mNameInput.text.toString(), mSourceUri.text.toString(), api!!)
if (config.baseUrl.isEmpty() || !config.baseUrl.isBaseUrlValid()) {
mustLoginToAddSource()
} else {
handleSpoutsSpinner(spoutsSpinner, api, progress, formContainer)
} }
}
private fun handleSpoutsSpinner(spoutsSpinner: Spinner, api: SelfossApi?, mProgress: ProgressBar, formContainer: ConstraintLayout) {
val spoutsKV = HashMap<String, String>() val spoutsKV = HashMap<String, String>()
mSpoutsSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { spoutsSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(adapterView: AdapterView<*>, view: View, i: Int, l: Long) { override fun onItemSelected(adapterView: AdapterView<*>, view: View, i: Int, l: Long) {
val spoutName = (view as TextView).text.toString() val spoutName = (view as TextView).text.toString()
mSpoutsValue = spoutsKV[spoutName] mSpoutsValue = spoutsKV[spoutName]
@ -71,48 +80,48 @@ class AddSourceActivity : AppCompatActivity() {
} }
} }
val config = Config(this) var items: Map<String, Spout>
api!!.spouts().enqueue(object : Callback<Map<String, Spout>> {
override fun onResponse(call: Call<Map<String, Spout>>, response: Response<Map<String, Spout>>) {
if (response.body() != null) {
items = response.body()!!
if (config.baseUrl.isEmpty() || !config.baseUrl.isUrlValid()) { val itemsStrings = items.map { it.value.name }
mustLoginToAddSource() for ((key, value) in items) {
} else { spoutsKV.put(value.name, key)
var items: Map<String, Spout>
api!!.spouts().enqueue(object : Callback<Map<String, Spout>> {
override fun onResponse(call: Call<Map<String, Spout>>, response: Response<Map<String, Spout>>) {
if (response.body() != null) {
items = response.body()!!
val itemsStrings = items.map { it.value.name }
for ((key, value) in items) {
spoutsKV.put(value.name, key)
}
mProgress.visibility = View.GONE
mForm.visibility = View.VISIBLE
val spinnerArrayAdapter =
ArrayAdapter(
this@AddSourceActivity,
android.R.layout.simple_spinner_item,
itemsStrings)
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
mSpoutsSpinner.adapter = spinnerArrayAdapter
} else {
handleProblemWithSpouts()
} }
}
override fun onFailure(call: Call<Map<String, Spout>>, t: Throwable) { 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 {
handleProblemWithSpouts() handleProblemWithSpouts()
} }
}
private fun handleProblemWithSpouts() { override fun onFailure(call: Call<Map<String, Spout>>, t: Throwable) {
Toast.makeText(this@AddSourceActivity, R.string.cant_get_spouts, Toast.LENGTH_SHORT).show() handleProblemWithSpouts()
mProgress.visibility = View.GONE }
}
}) private fun handleProblemWithSpouts() {
Toast.makeText(this@AddSourceActivity, R.string.cant_get_spouts, Toast.LENGTH_SHORT).show()
mProgress.visibility = View.GONE
}
})
}
private fun maybeGetDetailsFromIntentSharing(intent: Intent, sourceUri: EditText, nameInput: EditText) {
if (Intent.ACTION_SEND == intent.action && "text/plain" == intent.type) {
sourceUri.setText(intent.getStringExtra(Intent.EXTRA_TEXT))
nameInput.setText(intent.getStringExtra(Intent.EXTRA_TITLE))
} }
} }
@ -123,17 +132,19 @@ class AddSourceActivity : AppCompatActivity() {
finish() finish()
} }
private fun handleSaveSource(mTags: EditText, title: String, url: String, api: SelfossApi) { private fun handleSaveSource(tags: EditText, title: String, url: String, api: SelfossApi) {
if (title.isEmpty() || url.isEmpty() || mSpoutsValue == null || mSpoutsValue!!.isEmpty()) { val sourceDetailsAvailable = title.isEmpty() || url.isEmpty() || mSpoutsValue == null || mSpoutsValue!!.isEmpty()
if (sourceDetailsAvailable) {
Toast.makeText(this, R.string.form_not_complete, Toast.LENGTH_SHORT).show() Toast.makeText(this, R.string.form_not_complete, Toast.LENGTH_SHORT).show()
} else { } else {
api.createSource( api.createSource(
title, title,
url, url,
mSpoutsValue!!, mSpoutsValue!!,
mTags.text.toString(), tags.text.toString(),
"" ""
).enqueue(object : Callback<SuccessResponse> { ).enqueue(object : Callback<SuccessResponse> {
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) { override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {
if (response.body() != null && response.body()!!.isSuccess) { if (response.body() != null && response.body()!!.isSuccess) {

View File

@ -1,15 +1,14 @@
package apps.amine.bou.readerforselfoss package apps.amine.bou.readerforselfoss
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.preference.PreferenceManager
import android.view.View
import agency.tango.materialintroscreen.MaterialIntroActivity import agency.tango.materialintroscreen.MaterialIntroActivity
import agency.tango.materialintroscreen.MessageButtonBehaviour import agency.tango.materialintroscreen.MessageButtonBehaviour
import agency.tango.materialintroscreen.SlideFragmentBuilder import agency.tango.materialintroscreen.SlideFragmentBuilder
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.preference.PreferenceManager
import android.support.v7.app.AppCompatDelegate
import android.view.View
class IntroActivity : MaterialIntroActivity() { class IntroActivity : MaterialIntroActivity() {
@ -17,10 +16,12 @@ class IntroActivity : MaterialIntroActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
addSlide(SlideFragmentBuilder() addSlide(SlideFragmentBuilder()
.backgroundColor(R.color.colorPrimary) .backgroundColor(R.color.colorPrimary)
.buttonsColor(R.color.colorAccent) .buttonsColor(R.color.colorAccent)
.image(R.mipmap.ic_launcher) .image(R.drawable.web_hi_res_512)
.title(getString(R.string.intro_hello_title)) .title(getString(R.string.intro_hello_title))
.description(getString(R.string.intro_hello_message)) .description(getString(R.string.intro_hello_message))
.build()) .build())
@ -28,7 +29,7 @@ class IntroActivity : MaterialIntroActivity() {
addSlide(SlideFragmentBuilder() addSlide(SlideFragmentBuilder()
.backgroundColor(R.color.colorAccent) .backgroundColor(R.color.colorAccent)
.buttonsColor(R.color.colorPrimary) .buttonsColor(R.color.colorPrimary)
.image(R.drawable.ic_info_outline_white_48dp) .image(R.drawable.ic_info_outline_white_48px)
.title(getString(R.string.intro_needs_selfoss_title)) .title(getString(R.string.intro_needs_selfoss_title))
.description(getString(R.string.intro_needs_selfoss_message)) .description(getString(R.string.intro_needs_selfoss_message))
.build(), .build(),
@ -40,7 +41,7 @@ class IntroActivity : MaterialIntroActivity() {
addSlide(SlideFragmentBuilder() addSlide(SlideFragmentBuilder()
.backgroundColor(R.color.colorPrimaryDark) .backgroundColor(R.color.colorPrimaryDark)
.buttonsColor(R.color.colorAccentDark) .buttonsColor(R.color.colorAccentDark)
.image(R.drawable.ic_thumb_up_white_48dp) .image(R.drawable.ic_thumb_up_white_48px)
.title(getString(R.string.intro_all_set_title)) .title(getString(R.string.intro_all_set_title))
.description(getString(R.string.intro_all_set_message)) .description(getString(R.string.intro_all_set_message))
.build()) .build())

View File

@ -6,7 +6,6 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import android.support.design.widget.TextInputLayout
import android.support.v7.app.AlertDialog import android.support.v7.app.AlertDialog
import android.support.v7.app.AppCompatActivity import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar import android.support.v7.widget.Toolbar
@ -15,41 +14,36 @@ import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.widget.Button
import android.widget.EditText
import android.widget.Switch
import android.widget.TextView import android.widget.TextView
import android.widget.Toast
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
import apps.amine.bou.readerforselfoss.utils.Config
import apps.amine.bou.readerforselfoss.utils.isBaseUrlValid
import com.crashlytics.android.Crashlytics
import com.ftinc.scoop.Scoop
import com.google.firebase.analytics.FirebaseAnalytics import com.google.firebase.analytics.FirebaseAnalytics
import com.mikepenz.aboutlibraries.Libs import com.mikepenz.aboutlibraries.Libs
import com.mikepenz.aboutlibraries.LibsBuilder import com.mikepenz.aboutlibraries.LibsBuilder
import retrofit2.Call import retrofit2.Call
import retrofit2.Callback import retrofit2.Callback
import retrofit2.Response import retrofit2.Response
import kotlinx.android.synthetic.main.activity_login.*
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
import apps.amine.bou.readerforselfoss.utils.Config
import apps.amine.bou.readerforselfoss.utils.checkAndDisplayStoreApk
import apps.amine.bou.readerforselfoss.utils.isUrlValid
import com.ftinc.scoop.Scoop
class LoginActivity : AppCompatActivity() { class LoginActivity : AppCompatActivity() {
private var inValidCount: Int = 0 private var inValidCount: Int = 0
private var isWithSelfSignedCert = false
private var isWithLogin = false private var isWithLogin = false
private var isWithHTTPLogin = false private var isWithHTTPLogin = false
private lateinit var settings: SharedPreferences private lateinit var settings: SharedPreferences
private lateinit var mFirebaseAnalytics: FirebaseAnalytics private lateinit var editor: SharedPreferences.Editor
private lateinit var mUrlView: EditText private lateinit var firebaseAnalytics: FirebaseAnalytics
private lateinit var mLoginView: TextView private lateinit var userIdentifier: String
private lateinit var mHTTPLoginView: TextView private var logErrors: Boolean = false
private lateinit var mProgressView: View
private lateinit var mPasswordView: EditText
private lateinit var mHTTPPasswordView: EditText
private lateinit var mLoginFormView: View
@ -58,69 +52,72 @@ class LoginActivity : AppCompatActivity() {
Scoop.getInstance().apply(this) Scoop.getInstance().apply(this)
setContentView(R.layout.activity_login) setContentView(R.layout.activity_login)
val toolbar: Toolbar = findViewById(R.id.toolbar)
setSupportActionBar(toolbar) setSupportActionBar(toolbar)
if (intent.getBooleanExtra("baseUrlFail", false)) { handleBaseUrlFail()
val alertDialog = AlertDialog.Builder(this).create()
alertDialog.setTitle(getString(R.string.warning_wrong_url))
alertDialog.setMessage(getString(R.string.base_url_error))
alertDialog.setButton(
AlertDialog.BUTTON_NEUTRAL,
"OK",
{ dialog, _ -> dialog.dismiss() })
alertDialog.show()
}
settings = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE) settings = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
userIdentifier = settings.getString("unique_id", "")
logErrors = settings.getBoolean("login_debug", false)
editor = settings.edit()
if (settings.getString("url", "").isNotEmpty()) { if (settings.getString("url", "").isNotEmpty()) {
goToMain() goToMain()
} else {
this@LoginActivity.checkAndDisplayStoreApk()
} }
mFirebaseAnalytics = FirebaseAnalytics.getInstance(this) firebaseAnalytics = FirebaseAnalytics.getInstance(this)
mUrlView = findViewById(R.id.url)
mLoginView = findViewById(R.id.login)
mHTTPLoginView = findViewById(R.id.httpLogin)
mPasswordView = findViewById(R.id.password)
mHTTPPasswordView = findViewById(R.id.httpPassword)
mLoginFormView = findViewById(R.id.login_form)
mProgressView = findViewById(R.id.login_progress)
val mSwitch: Switch = findViewById(R.id.withLogin) handleActions()
val mHTTPSwitch: Switch = findViewById(R.id.withHttpLogin) }
val mLoginLayout: TextInputLayout = findViewById(R.id.loginLayout)
val mHTTPLoginLayout: TextInputLayout = findViewById(R.id.httpLoginInput)
val mPasswordLayout: TextInputLayout = findViewById(R.id.passwordLayout)
val mHTTPPasswordLayout: TextInputLayout = findViewById(R.id.httpPasswordInput)
val mEmailSignInButton: Button = findViewById(R.id.email_sign_in_button)
mPasswordView.setOnEditorActionListener(TextView.OnEditorActionListener { _, id, _ -> private fun handleActions() {
if (id == R.id.login || id == EditorInfo.IME_NULL) {
withSelfhostedCert.setOnCheckedChangeListener { _, b ->
isWithSelfSignedCert = !isWithSelfSignedCert
val visi: Int = if (b) View.VISIBLE else View.GONE
warningText.visibility = visi
}
passwordView.setOnEditorActionListener(TextView.OnEditorActionListener { _, id, _ ->
if (id == R.id.loginView || id == EditorInfo.IME_NULL) {
attemptLogin() attemptLogin()
return@OnEditorActionListener true return@OnEditorActionListener true
} }
false false
}) })
mEmailSignInButton.setOnClickListener { attemptLogin() } signInButton.setOnClickListener { attemptLogin() }
mSwitch.setOnCheckedChangeListener { _, b -> withLogin.setOnCheckedChangeListener { _, b ->
isWithLogin = !isWithLogin isWithLogin = !isWithLogin
val visi: Int = if (b) View.VISIBLE else View.GONE val visi: Int = if (b) View.VISIBLE else View.GONE
mLoginLayout.visibility = visi loginLayout.visibility = visi
mPasswordLayout.visibility = visi passwordLayout.visibility = visi
} }
mHTTPSwitch.setOnCheckedChangeListener { _, b -> withHttpLogin.setOnCheckedChangeListener { _, b ->
isWithHTTPLogin = !isWithHTTPLogin isWithHTTPLogin = !isWithHTTPLogin
val visi: Int = if (b) View.VISIBLE else View.GONE val visi: Int = if (b) View.VISIBLE else View.GONE
mHTTPLoginLayout.visibility = visi httpLoginInput.visibility = visi
mHTTPPasswordLayout.visibility = visi httpPasswordInput.visibility = visi
}
}
private fun handleBaseUrlFail() {
if (intent.getBooleanExtra("baseUrlFail", false)) {
val alertDialog = AlertDialog.Builder(this).create()
alertDialog.setTitle(getString(R.string.warning_wrong_url))
alertDialog.setMessage(getString(R.string.base_url_error))
alertDialog.setButton(
AlertDialog.BUTTON_NEUTRAL,
"OK",
{ dialog, _ -> dialog.dismiss() })
alertDialog.show()
} }
} }
@ -133,25 +130,25 @@ class LoginActivity : AppCompatActivity() {
private fun attemptLogin() { private fun attemptLogin() {
// Reset errors. // Reset errors.
mUrlView.error = null urlView.error = null
mLoginView.error = null loginView.error = null
mHTTPLoginView.error = null httpLoginView.error = null
mPasswordView.error = null passwordView.error = null
mHTTPPasswordView.error = null httpPasswordView.error = null
// Store values at the time of the login attempt. // Store values at the time of the login attempt.
val url = mUrlView.text.toString() val url = urlView.text.toString()
val login = mLoginView.text.toString() val login = loginView.text.toString()
val httpLogin = mHTTPLoginView.text.toString() val httpLogin = httpLoginView.text.toString()
val password = mPasswordView.text.toString() val password = passwordView.text.toString()
val httpPassword = mHTTPPasswordView.text.toString() val httpPassword = httpPasswordView.text.toString()
var cancel = false var cancel = false
var focusView: View? = null var focusView: View? = null
if (!url.isUrlValid()) { if (!url.isBaseUrlValid()) {
mUrlView.error = getString(R.string.login_url_problem) urlView.error = getString(R.string.login_url_problem)
focusView = mUrlView focusView = urlView
cancel = true cancel = true
inValidCount++ inValidCount++
if (inValidCount == 3) { if (inValidCount == 3) {
@ -159,9 +156,9 @@ class LoginActivity : AppCompatActivity() {
alertDialog.setTitle(getString(R.string.warning_wrong_url)) alertDialog.setTitle(getString(R.string.warning_wrong_url))
alertDialog.setMessage(getString(R.string.text_wrong_url)) alertDialog.setMessage(getString(R.string.text_wrong_url))
alertDialog.setButton( alertDialog.setButton(
AlertDialog.BUTTON_NEUTRAL, AlertDialog.BUTTON_NEUTRAL,
"OK", "OK",
{ dialog, _ -> dialog.dismiss() }) { dialog, _ -> dialog.dismiss() })
alertDialog.show() alertDialog.show()
inValidCount = 0 inValidCount = 0
} }
@ -169,14 +166,14 @@ class LoginActivity : AppCompatActivity() {
if (isWithLogin || isWithHTTPLogin) { if (isWithLogin || isWithHTTPLogin) {
if (TextUtils.isEmpty(password)) { if (TextUtils.isEmpty(password)) {
mPasswordView.error = getString(R.string.error_invalid_password) passwordView.error = getString(R.string.error_invalid_password)
focusView = mPasswordView focusView = passwordView
cancel = true cancel = true
} }
if (TextUtils.isEmpty(login)) { if (TextUtils.isEmpty(login)) {
mLoginView.error = getString(R.string.error_field_required) loginView.error = getString(R.string.error_field_required)
focusView = mLoginView focusView = loginView
cancel = true cancel = true
} }
} }
@ -186,80 +183,85 @@ class LoginActivity : AppCompatActivity() {
} else { } else {
showProgress(true) showProgress(true)
val editor = settings.edit()
editor.putString("url", url) editor.putString("url", url)
editor.putString("login", login) editor.putString("login", login)
editor.putString("httpUserName", httpLogin) editor.putString("httpUserName", httpLogin)
editor.putString("password", password) editor.putString("password", password)
editor.putString("httpPassword", httpPassword) editor.putString("httpPassword", httpPassword)
editor.putBoolean("isSelfSignedCert", isWithSelfSignedCert)
editor.apply() editor.apply()
val api = SelfossApi(this, this@LoginActivity) val api = SelfossApi(this, this@LoginActivity, isWithSelfSignedCert, isWithSelfSignedCert)
api.login().enqueue(object : Callback<SuccessResponse> { api.login().enqueue(object : Callback<SuccessResponse> {
private fun preferenceError() { private fun preferenceError(t: Throwable) {
editor.remove("url") editor.remove("url")
editor.remove("login") editor.remove("login")
editor.remove("httpUserName") editor.remove("httpUserName")
editor.remove("password") editor.remove("password")
editor.remove("httpPassword") editor.remove("httpPassword")
editor.apply() editor.apply()
mUrlView.error = getString(R.string.wrong_infos) urlView.error = getString(R.string.wrong_infos)
mLoginView.error = getString(R.string.wrong_infos) loginView.error = getString(R.string.wrong_infos)
mPasswordView.error = getString(R.string.wrong_infos) passwordView.error = getString(R.string.wrong_infos)
mHTTPLoginView.error = getString(R.string.wrong_infos) httpLoginView.error = getString(R.string.wrong_infos)
mHTTPPasswordView.error = getString(R.string.wrong_infos) httpPasswordView.error = getString(R.string.wrong_infos)
if (logErrors) {
Crashlytics.setUserIdentifier(userIdentifier)
Crashlytics.log(100, "LOGIN_DEBUG_ERRROR", t.message)
Crashlytics.logException(t)
Toast.makeText(this@LoginActivity, t.message, Toast.LENGTH_LONG).show()
}
showProgress(false) showProgress(false)
} }
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) { override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {
if (response.body() != null && response.body()!!.isSuccess) { if (response.body() != null && response.body()!!.isSuccess) {
mFirebaseAnalytics.logEvent(FirebaseAnalytics.Event.LOGIN, Bundle()) firebaseAnalytics.logEvent(FirebaseAnalytics.Event.LOGIN, Bundle())
goToMain() goToMain()
} else { } else {
preferenceError() preferenceError(Exception("No response body..."))
} }
} }
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) { override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
preferenceError() preferenceError(t)
} }
}) })
} }
} }
/**
* Shows the progress UI and hides the login form.
*/
private fun showProgress(show: Boolean) { private fun showProgress(show: Boolean) {
val shortAnimTime = resources.getInteger(android.R.integer.config_shortAnimTime) val shortAnimTime = resources.getInteger(android.R.integer.config_shortAnimTime)
mLoginFormView.visibility = if (show) View.GONE else View.VISIBLE loginForm.visibility = if (show) View.GONE else View.VISIBLE
mLoginFormView loginForm
.animate() .animate()
.setDuration(shortAnimTime.toLong()) .setDuration(shortAnimTime.toLong())
.alpha( .alpha(
if (show) 0F else 1F if (show) 0F else 1F
).setListener(object : AnimatorListenerAdapter() { ).setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) { override fun onAnimationEnd(animation: Animator) {
mLoginFormView.visibility = if (show) View.GONE else View.VISIBLE loginForm.visibility = if (show) View.GONE else View.VISIBLE
} }
}) })
mProgressView.visibility = if (show) View.VISIBLE else View.GONE loginProgress.visibility = if (show) View.VISIBLE else View.GONE
mProgressView loginProgress
.animate() .animate()
.setDuration(shortAnimTime.toLong()) .setDuration(shortAnimTime.toLong())
.alpha( .alpha(
if (show) 1F else 0F if (show) 1F else 0F
).setListener(object : AnimatorListenerAdapter() { ).setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) { override fun onAnimationEnd(animation: Animator) {
mProgressView.visibility = if (show) View.VISIBLE else View.GONE loginProgress.visibility = if (show) View.VISIBLE else View.GONE
} }
}) })
} }
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.login_menu, menu) menuInflater.inflate(R.menu.login_menu, menu)
menu.findItem(R.id.login_debug).isChecked = logErrors
return true return true
} }
@ -267,10 +269,18 @@ class LoginActivity : AppCompatActivity() {
when (item.itemId) { when (item.itemId) {
R.id.about -> { R.id.about -> {
LibsBuilder() LibsBuilder()
.withActivityStyle(Libs.ActivityStyle.LIGHT_DARK_TOOLBAR) .withActivityStyle(Libs.ActivityStyle.LIGHT_DARK_TOOLBAR)
.withAboutIconShown(true) .withAboutIconShown(true)
.withAboutVersionShown(true) .withAboutVersionShown(true)
.start(this) .start(this)
return true
}
R.id.login_debug -> {
val newState = !item.isChecked
item.isChecked = newState
logErrors = newState
editor.putBoolean("login_debug", newState)
editor.apply()
return true return true
} }
else -> return super.onOptionsItemSelected(item) else -> return super.onOptionsItemSelected(item)

View File

@ -6,47 +6,85 @@ import android.net.Uri
import android.preference.PreferenceManager import android.preference.PreferenceManager
import android.support.multidex.MultiDexApplication import android.support.multidex.MultiDexApplication
import android.widget.ImageView import android.widget.ImageView
import apps.amine.bou.readerforselfoss.utils.Config
import com.anupcowkur.reservoir.Reservoir import com.anupcowkur.reservoir.Reservoir
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import com.crashlytics.android.Crashlytics import com.crashlytics.android.Crashlytics
import com.ftinc.scoop.Scoop import com.ftinc.scoop.Scoop
import com.github.stkent.amplify.feedback.DefaultEmailFeedbackCollector
import com.github.stkent.amplify.feedback.GooglePlayStoreFeedbackCollector
import com.github.stkent.amplify.tracking.Amplify import com.github.stkent.amplify.tracking.Amplify
import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader
import com.mikepenz.materialdrawer.util.DrawerImageLoader import com.mikepenz.materialdrawer.util.DrawerImageLoader
import io.fabric.sdk.android.Fabric import io.fabric.sdk.android.Fabric
import java.io.IOException import java.io.IOException
import java.util.UUID.randomUUID
class MyApp : MultiDexApplication() { class MyApp : MultiDexApplication() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
if (!BuildConfig.DEBUG) Fabric.with(this, Crashlytics())
Fabric.with(this, Crashlytics())
initAmplify()
initCache()
val prefs = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
if (prefs.getString("unique_id", "").isEmpty()) {
val editor = prefs.edit()
editor.putString("unique_id", randomUUID().toString())
editor.apply()
}
initDrawerImageLoader()
initTheme()
tryToHandleBug()
}
private fun initAmplify() {
Amplify.initSharedInstance(this) Amplify.initSharedInstance(this)
.setFeedbackEmailAddress(getString(R.string.feedback_email)) .setPositiveFeedbackCollectors(GooglePlayStoreFeedbackCollector())
.setAlwaysShow(BuildConfig.DEBUG) .setCriticalFeedbackCollectors(DefaultEmailFeedbackCollector(BuildConfig.FEEDBACK_EMAIL))
.applyAllDefaultRules() .applyAllDefaultRules()
}
private fun initCache() {
try { try {
Reservoir.init(this, 8192) //in bytes Reservoir.init(this, 8192) //in bytes
} catch (e: IOException) { } catch (e: IOException) {
//failure //failure
} }
}
private fun initDrawerImageLoader() {
DrawerImageLoader.init(object : AbstractDrawerImageLoader() { DrawerImageLoader.init(object : AbstractDrawerImageLoader() {
override fun set(imageView: ImageView?, uri: Uri?, placeholder: Drawable?, tag: String?) { override fun set(imageView: ImageView?, uri: Uri?, placeholder: Drawable?, tag: String?) {
Glide.with(imageView?.context).load(uri).placeholder(placeholder).into(imageView) Glide.with(imageView?.context)
.load(uri)
.apply(RequestOptions.fitCenterTransform()
.placeholder(placeholder))
.into(imageView)
} }
override fun cancel(imageView: ImageView?) { override fun cancel(imageView: ImageView?) {
Glide.clear(imageView) Glide.with(imageView?.context).clear(imageView)
} }
override fun placeholder(ctx: Context?, tag: String?): Drawable { override fun placeholder(ctx: Context?, tag: String?): Drawable {
return baseContext.resources.getDrawable(R.mipmap.ic_launcher) return baseContext.resources.getDrawable(R.mipmap.ic_launcher)
} }
}) })
}
private fun initTheme() {
Scoop.waffleCone() Scoop.waffleCone()
.addFlavor(getString(R.string.default_theme), R.style.NoBar, true) .addFlavor(getString(R.string.default_theme), R.style.NoBar, true)
.addFlavor(getString(R.string.default_dark_theme), R.style.NoBarDark) .addFlavor(getString(R.string.default_dark_theme), R.style.NoBarDark)
@ -64,6 +102,17 @@ class MyApp : MultiDexApplication() {
.addFlavor(getString(R.string.red_teal_dark_theme), R.style.NoBarRedTealDark) .addFlavor(getString(R.string.red_teal_dark_theme), R.style.NoBarRedTealDark)
.setSharedPreferences(PreferenceManager.getDefaultSharedPreferences(this)) .setSharedPreferences(PreferenceManager.getDefaultSharedPreferences(this))
.initialize() .initialize()
}
private fun tryToHandleBug() {
val oldHandler = Thread.getDefaultUncaughtExceptionHandler()
Thread.setDefaultUncaughtExceptionHandler { thread, e ->
if (e is java.lang.NoClassDefFoundError && e.stackTrace.asList().any { it.toString().contains("android.view.ViewDebug") })
Unit
else
oldHandler.uncaughtException(thread, e)
}
} }
} }

View File

@ -1,114 +1,190 @@
package apps.amine.bou.readerforselfoss package apps.amine.bou.readerforselfoss
import android.content.Intent import android.content.SharedPreferences
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.preference.PreferenceManager
import android.support.customtabs.CustomTabsIntent
import android.support.design.widget.FloatingActionButton
import android.support.v4.widget.NestedScrollView
import android.support.v7.app.AppCompatActivity
import android.text.Html
import android.text.method.LinkMovementMethod
import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.TextView
import com.bumptech.glide.Glide
import org.sufficientlysecure.htmltextview.HtmlHttpImageGetter
import org.sufficientlysecure.htmltextview.HtmlTextView
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import xyz.klinker.android.drag_dismiss.activity.DragDismissActivity
import apps.amine.bou.readerforselfoss.api.mercury.MercuryApi import apps.amine.bou.readerforselfoss.api.mercury.MercuryApi
import apps.amine.bou.readerforselfoss.api.mercury.ParsedContent import apps.amine.bou.readerforselfoss.api.mercury.ParsedContent
import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
import apps.amine.bou.readerforselfoss.utils.isEmptyOrNullOrNullString
import apps.amine.bou.readerforselfoss.utils.openItemUrl import apps.amine.bou.readerforselfoss.utils.openItemUrl
import apps.amine.bou.readerforselfoss.utils.shareLink import apps.amine.bou.readerforselfoss.utils.shareLink
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import com.crashlytics.android.Crashlytics
import com.ftinc.scoop.Scoop import com.ftinc.scoop.Scoop
import com.github.rubensousa.floatingtoolbar.FloatingToolbar
import org.sufficientlysecure.htmltextview.HtmlHttpImageGetter
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import kotlinx.android.synthetic.main.activity_reader.*
class ReaderActivity : DragDismissActivity() { class ReaderActivity : AppCompatActivity() {
private lateinit var mCustomTabActivityHelper: CustomTabActivityHelper private lateinit var mCustomTabActivityHelper: CustomTabActivityHelper
//private lateinit var content: HtmlTextView
private lateinit var url: String
private lateinit var contentText: String
private lateinit var contentSource: String
private lateinit var contentImage: String
private lateinit var contentTitle: String
private lateinit var fab: FloatingActionButton
override fun onStart() {
super.onStart()
mCustomTabActivityHelper.bindCustomTabsService(this)
}
override fun onStop() { override fun onStop() {
super.onStop() super.onStop()
mCustomTabActivityHelper.unbindCustomTabsService(this) mCustomTabActivityHelper.unbindCustomTabsService(this)
} }
override fun onCreateContent(inflater: LayoutInflater, parent: ViewGroup, savedInstanceState: Bundle?): View { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Scoop.getInstance().apply(this) Scoop.getInstance().apply(this)
val v = inflater.inflate(R.layout.activity_reader, parent, false) setContentView(R.layout.activity_reader)
showProgressBar()
val image: ImageView = v.findViewById(R.id.imageView) url = intent.getStringExtra("url")
val source: TextView = v.findViewById(R.id.source) contentText = intent.getStringExtra("content")
val title: TextView = v.findViewById(R.id.title) contentTitle = intent.getStringExtra("title")
val content: HtmlTextView = v.findViewById(R.id.content) contentImage = intent.getStringExtra("image")
val url = intent.getStringExtra("url") contentSource = intent.getStringExtra("source")
val parser = MercuryApi(getString(R.string.mercury))
val browserBtn: ImageButton = v.findViewById(R.id.browserBtn)
val shareBtn: ImageButton = v.findViewById(R.id.shareBtn)
fab = findViewById(R.id.fab)
val mFloatingToolbar: FloatingToolbar = findViewById(R.id.floatingToolbar)
mFloatingToolbar.attachFab(fab)
val customTabsIntent = this@ReaderActivity.buildCustomTabsIntent() val customTabsIntent = this@ReaderActivity.buildCustomTabsIntent()
mCustomTabActivityHelper = CustomTabActivityHelper() mCustomTabActivityHelper = CustomTabActivityHelper()
mCustomTabActivityHelper.bindCustomTabsService(this) mCustomTabActivityHelper.bindCustomTabsService(this)
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
mFloatingToolbar.setClickListener(object : FloatingToolbar.ItemClickListener {
override fun onItemClick(item: MenuItem) {
when (item.itemId) {
R.id.more_action -> getContentFromMercury(customTabsIntent, prefs)
R.id.share_action -> this@ReaderActivity.shareLink(url)
R.id.open_action -> this@ReaderActivity.openItemUrl(
url,
contentText,
contentImage,
contentTitle,
contentSource,
customTabsIntent,
false,
false,
this@ReaderActivity)
else -> Unit
}
}
override fun onItemLongClick(item: MenuItem?) {
}
})
if (contentText.isEmptyOrNullOrNullString()) {
getContentFromMercury(customTabsIntent, prefs)
} else {
source.text = contentSource
titleView.text = contentTitle
tryToHandleHtml(contentText, customTabsIntent, prefs)
if (!contentImage.isEmptyOrNullOrNullString()) {
imageView.visibility = View.VISIBLE
Glide
.with(baseContext)
.asBitmap()
.load(contentImage)
.apply(RequestOptions.fitCenterTransform())
.into(imageView)
} else {
imageView.visibility = View.GONE
}
}
nestedScrollView.setOnScrollChangeListener(NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
if (scrollY > oldScrollY) {
fab.hide()
} else {
if (mFloatingToolbar.isShowing) mFloatingToolbar.hide() else fab.show()
}
})
content.movementMethod = LinkMovementMethod.getInstance()
}
private fun getContentFromMercury(customTabsIntent: CustomTabsIntent, prefs: SharedPreferences) {
progressBar.visibility = View.VISIBLE
val parser = MercuryApi(BuildConfig.MERCURY_KEY, prefs.getBoolean("should_log_everything", false))
parser.parseUrl(url).enqueue(object : Callback<ParsedContent> { parser.parseUrl(url).enqueue(object : Callback<ParsedContent> {
override fun onResponse(call: Call<ParsedContent>, response: Response<ParsedContent>) { override fun onResponse(call: Call<ParsedContent>, response: Response<ParsedContent>) {
if (response.body() != null && response.body()!!.content != null && response.body()!!.content.isNotEmpty()) { if (response.body() != null && response.body()!!.content != null && response.body()!!.content.isNotEmpty()) {
source.text = response.body()!!.domain source.text = response.body()!!.domain
title.text = response.body()!!.title titleView.text = response.body()!!.title
this@ReaderActivity.url = response.body()!!.url
if (response.body()!!.content != null && !response.body()!!.content.isEmpty()) { if (response.body()!!.content != null && !response.body()!!.content.isEmpty()) {
try { tryToHandleHtml(response.body()!!.content, customTabsIntent, prefs)
content.setHtml(response.body()!!.content, HtmlHttpImageGetter(content, null, true))
} catch (e: IndexOutOfBoundsException) {
openInBrowserAfterFailing()
}
} }
if (response.body()!!.lead_image_url != null && !response.body()!!.lead_image_url.isEmpty())
if (response.body()!!.lead_image_url != null && !response.body()!!.lead_image_url.isEmpty()) {
imageView.visibility = View.VISIBLE
Glide Glide
.with(baseContext) .with(baseContext)
.load(response.body()!!.lead_image_url) .asBitmap()
.asBitmap() .load(response.body()!!.lead_image_url)
.fitCenter() .apply(RequestOptions.fitCenterTransform())
.into(image) .into(imageView)
} else {
shareBtn.setOnClickListener { imageView.visibility = View.GONE
this@ReaderActivity.shareLink(response.body()!!.url)
} }
browserBtn.setOnClickListener { nestedScrollView.scrollTo(0, 0)
this@ReaderActivity.openItemUrl(
response.body()!!.url,
customTabsIntent,
false,
false,
this@ReaderActivity)
}
hideProgressBar() progressBar.visibility = View.GONE
} else openInBrowserAfterFailing() } else openInBrowserAfterFailing(customTabsIntent)
} }
override fun onFailure(call: Call<ParsedContent>, t: Throwable) = openInBrowserAfterFailing() override fun onFailure(call: Call<ParsedContent>, t: Throwable) = openInBrowserAfterFailing(customTabsIntent)
private fun openInBrowserAfterFailing() {
CustomTabActivityHelper.openCustomTab(this@ReaderActivity, customTabsIntent, Uri.parse(url)
) { _, uri ->
val intent = Intent(Intent.ACTION_VIEW, uri)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
startActivity(intent)
}
finish()
}
}) })
return v }
private fun tryToHandleHtml(c: String, customTabsIntent: CustomTabsIntent, prefs: SharedPreferences) {
try {
content.text = Html.fromHtml(c, HtmlHttpImageGetter(content, null, true), null)
//content.setHtml(response.body()!!.content, HtmlHttpImageGetter(content, null, true))
} catch (e: Exception) {
Crashlytics.setUserIdentifier(prefs.getString("unique_id", ""))
Crashlytics.log(100, "CANT_TRANSFORM_TO_HTML", e.message)
Crashlytics.logException(e)
openInBrowserAfterFailing(customTabsIntent)
}
}
private fun openInBrowserAfterFailing(customTabsIntent: CustomTabsIntent) {
progressBar.visibility = View.GONE
this@ReaderActivity.openItemUrl(
url,
contentText,
contentImage,
contentTitle,
contentSource,
customTabsIntent,
true,
false,
this@ReaderActivity
)
finish()
} }
} }

View File

@ -2,46 +2,48 @@ package apps.amine.bou.readerforselfoss
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.preference.PreferenceManager
import android.support.v7.app.AppCompatActivity import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.Toolbar
import android.widget.Toast import android.widget.Toast
import com.melnykov.fab.FloatingActionButton
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import apps.amine.bou.readerforselfoss.adapters.SourcesListAdapter import apps.amine.bou.readerforselfoss.adapters.SourcesListAdapter
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
import apps.amine.bou.readerforselfoss.api.selfoss.Sources import apps.amine.bou.readerforselfoss.api.selfoss.Sources
import com.ftinc.scoop.Scoop import com.ftinc.scoop.Scoop
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import kotlinx.android.synthetic.main.activity_sources.*
class SourcesActivity : AppCompatActivity() { class SourcesActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
Scoop.getInstance().apply(this) Scoop.getInstance().apply(this)
setContentView(R.layout.activity_sources) setContentView(R.layout.activity_sources)
val toolbar: Toolbar = findViewById(R.id.toolbar)
setSupportActionBar(toolbar) setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true) supportActionBar?.setDisplayShowHomeEnabled(true)
} }
override fun onStop() {
super.onStop()
recyclerView.clearOnScrollListeners()
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
val mFab: FloatingActionButton = findViewById(R.id.fab)
val mRecyclerView: RecyclerView = findViewById(R.id.activity_sources)
val mLayoutManager = LinearLayoutManager(this) val mLayoutManager = LinearLayoutManager(this)
val api = SelfossApi(this, this@SourcesActivity)
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
val api = SelfossApi(this, this@SourcesActivity, prefs.getBoolean("isSelfSignedCert", false), prefs.getBoolean("should_log_everything", false))
var items: ArrayList<Sources> = ArrayList() var items: ArrayList<Sources> = ArrayList()
mFab.attachToRecyclerView(mRecyclerView) recyclerView.setHasFixedSize(true)
mRecyclerView.setHasFixedSize(true) recyclerView.layoutManager = mLayoutManager
mRecyclerView.layoutManager = mLayoutManager
api.sources.enqueue(object : Callback<List<Sources>> { api.sources.enqueue(object : Callback<List<Sources>> {
override fun onResponse(call: Call<List<Sources>>, response: Response<List<Sources>>) { override fun onResponse(call: Call<List<Sources>>, response: Response<List<Sources>>) {
@ -49,7 +51,7 @@ class SourcesActivity : AppCompatActivity() {
items = response.body() as ArrayList<Sources> items = response.body() as ArrayList<Sources>
} }
val mAdapter = SourcesListAdapter(this@SourcesActivity, items, api) val mAdapter = SourcesListAdapter(this@SourcesActivity, items, api)
mRecyclerView.adapter = mAdapter recyclerView.adapter = mAdapter
mAdapter.notifyDataSetChanged() mAdapter.notifyDataSetChanged()
if (items.isEmpty()) Toast.makeText(this@SourcesActivity, R.string.nothing_here, Toast.LENGTH_SHORT).show() if (items.isEmpty()) Toast.makeText(this@SourcesActivity, R.string.nothing_here, Toast.LENGTH_SHORT).show()
} }
@ -59,7 +61,7 @@ class SourcesActivity : AppCompatActivity() {
} }
}) })
mFab.setOnClickListener { fab.setOnClickListener {
startActivity(Intent(this@SourcesActivity, AddSourceActivity::class.java)) startActivity(Intent(this@SourcesActivity, AddSourceActivity::class.java))
} }
} }

View File

@ -2,43 +2,41 @@ package apps.amine.bou.readerforselfoss.adapters
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.graphics.Bitmap
import android.graphics.Color import android.graphics.Color
import android.support.constraint.ConstraintLayout
import android.support.design.widget.Snackbar import android.support.design.widget.Snackbar
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory
import android.support.v7.widget.CardView import android.support.v7.widget.CardView
import android.support.v7.widget.RecyclerView import android.support.v7.widget.RecyclerView
import android.text.Html import android.text.Html
import android.text.format.DateUtils
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.ImageView.ScaleType import android.widget.ImageView.ScaleType
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import com.amulyakhare.textdrawable.TextDrawable
import com.amulyakhare.textdrawable.util.ColorGenerator
import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.BitmapImageViewTarget
import com.like.LikeButton
import com.like.OnLikeListener
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.*
import apps.amine.bou.readerforselfoss.R import apps.amine.bou.readerforselfoss.R
import apps.amine.bou.readerforselfoss.api.selfoss.Item import apps.amine.bou.readerforselfoss.api.selfoss.Item
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
import apps.amine.bou.readerforselfoss.themes.AppColors import apps.amine.bou.readerforselfoss.themes.AppColors
import apps.amine.bou.readerforselfoss.utils.* import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
import apps.amine.bou.readerforselfoss.utils.glide.bitmapCenterCrop
import apps.amine.bou.readerforselfoss.utils.glide.circularBitmapDrawable
import apps.amine.bou.readerforselfoss.utils.openInBrowserAsNewTask
import apps.amine.bou.readerforselfoss.utils.openItemUrl
import apps.amine.bou.readerforselfoss.utils.shareLink
import apps.amine.bou.readerforselfoss.utils.sourceAndDateText
import apps.amine.bou.readerforselfoss.utils.succeeded
import apps.amine.bou.readerforselfoss.utils.toTextDrawableString
import com.amulyakhare.textdrawable.TextDrawable
import com.amulyakhare.textdrawable.util.ColorGenerator
import com.bumptech.glide.Glide
import com.crashlytics.android.Crashlytics
import com.like.LikeButton
import com.like.OnLikeListener
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import kotlinx.android.synthetic.main.card_item.view.*
class ItemCardAdapter(private val app: Activity, class ItemCardAdapter(private val app: Activity,
private val items: ArrayList<Item>, private val items: ArrayList<Item>,
@ -47,12 +45,14 @@ class ItemCardAdapter(private val app: Activity,
private val internalBrowser: Boolean, private val internalBrowser: Boolean,
private val articleViewer: Boolean, private val articleViewer: Boolean,
private val fullHeightCards: Boolean, private val fullHeightCards: Boolean,
private val appColors: AppColors) : RecyclerView.Adapter<ItemCardAdapter.ViewHolder>() { private val appColors: AppColors,
val debugReadingItems: Boolean,
val userIdentifier: String) : RecyclerView.Adapter<ItemCardAdapter.ViewHolder>() {
private val c: Context = app.baseContext private val c: Context = app.baseContext
private val generator: ColorGenerator = ColorGenerator.MATERIAL private val generator: ColorGenerator = ColorGenerator.MATERIAL
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(c).inflate(R.layout.card_item, parent, false) as ConstraintLayout val v = LayoutInflater.from(c).inflate(R.layout.card_item, parent, false) as CardView
return ViewHolder(v) return ViewHolder(v)
} }
@ -60,37 +60,32 @@ class ItemCardAdapter(private val app: Activity,
val itm = items[position] val itm = items[position]
holder.saveBtn.isLiked = itm.starred holder.mView.favButton.isLiked = itm.starred
holder.title.text = Html.fromHtml(itm.title) holder.mView.title.text = Html.fromHtml(itm.title)
holder.sourceTitleAndDate.text = itm.sourceAndDateText() holder.mView.sourceTitleAndDate.text = itm.sourceAndDateText()
if (itm.getThumbnail(c).isEmpty()) { if (itm.getThumbnail(c).isEmpty()) {
Glide.clear(holder.itemImage) Glide.with(c).clear(holder.mView.itemImage)
holder.itemImage.setImageDrawable(null) holder.mView.itemImage.setImageDrawable(null)
} else { } else {
if (fullHeightCards) { c.bitmapCenterCrop(itm.getThumbnail(c), holder.mView.itemImage)
c.bitmapFitCenter(itm.getThumbnail(c), holder.itemImage)
} else {
c.bitmapCenterCrop(itm.getThumbnail(c), holder.itemImage)
}
} }
val fHolder = holder
if (itm.getIcon(c).isEmpty()) { if (itm.getIcon(c).isEmpty()) {
val color = generator.getColor(itm.sourcetitle) val color = generator.getColor(itm.sourcetitle)
val drawable = val drawable =
TextDrawable TextDrawable
.builder() .builder()
.round() .round()
.build(itm.sourcetitle.toTextDrawableString(), color) .build(itm.sourcetitle.toTextDrawableString(), color)
holder.sourceImage.setImageDrawable(drawable) holder.mView.sourceImage.setImageDrawable(drawable)
} else { } else {
c.circularBitmapDrawable(itm.getIcon(c), holder.sourceImage) c.circularBitmapDrawable(itm.getIcon(c), holder.mView.sourceImage)
} }
holder.saveBtn.isLiked = itm.starred holder.mView.favButton.isLiked = itm.starred
} }
override fun getItemCount(): Int { override fun getItemCount(): Int {
@ -130,11 +125,31 @@ class ItemCardAdapter(private val app: Activity,
api.markItem(i.id).enqueue(object : Callback<SuccessResponse> { api.markItem(i.id).enqueue(object : Callback<SuccessResponse> {
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) { override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {
if (!response.succeeded() && debugReadingItems) {
val message =
"message: ${response.message()} " +
"response isSuccess: ${response.isSuccessful} " +
"response code: ${response.code()} " +
"response message: ${response.message()} " +
"response errorBody: ${response.errorBody()?.string()} " +
"body success: ${response.body()?.success} " +
"body isSuccess: ${response.body()?.isSuccess}"
Crashlytics.setUserIdentifier(userIdentifier)
Crashlytics.log(100, "READ_DEBUG_SUCCESS", message)
Crashlytics.logException(Exception("Was success, but did it work ?"))
Toast.makeText(c, message, Toast.LENGTH_LONG).show()
}
doUnmark(i, position) doUnmark(i, position)
} }
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) { override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
if (debugReadingItems) {
Crashlytics.setUserIdentifier(userIdentifier)
Crashlytics.log(100, "READ_DEBUG_ERROR", t.message)
Crashlytics.logException(t)
Toast.makeText(c, t.message, Toast.LENGTH_LONG).show()
}
Toast.makeText(app, app.getString(R.string.cant_mark_read), Toast.LENGTH_SHORT).show() Toast.makeText(app, app.getString(R.string.cant_mark_read), Toast.LENGTH_SHORT).show()
items.add(i) items.add(i)
notifyItemInserted(position) notifyItemInserted(position)
@ -143,43 +158,28 @@ class ItemCardAdapter(private val app: Activity,
} }
inner class ViewHolder(val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) { inner class ViewHolder(val mView: CardView) : RecyclerView.ViewHolder(mView) {
lateinit var saveBtn: LikeButton
lateinit var browserBtn: ImageButton
lateinit var shareBtn: ImageButton
lateinit var itemImage: ImageView
lateinit var sourceImage: ImageView
lateinit var title: TextView
lateinit var sourceTitleAndDate: TextView
init { init {
(mView.findViewById<CardView>(R.id.card)).setCardBackgroundColor(appColors.cardBackground) mView.setCardBackgroundColor(appColors.cardBackground)
handleClickListeners() handleClickListeners()
handleCustomTabActions() handleCustomTabActions()
} }
private fun handleClickListeners() { private fun handleClickListeners() {
sourceImage = mView.findViewById(R.id.sourceImage)
itemImage = mView.findViewById(R.id.itemImage)
title = mView.findViewById(R.id.title)
sourceTitleAndDate = mView.findViewById(R.id.sourceTitleAndDate)
saveBtn = mView.findViewById(R.id.favButton)
shareBtn = mView.findViewById(R.id.shareBtn)
browserBtn = mView.findViewById(R.id.browserBtn)
if (!fullHeightCards) { if (!fullHeightCards) {
itemImage.maxHeight = c.resources.getDimension(R.dimen.card_image_max_height).toInt() mView.itemImage.maxHeight = c.resources.getDimension(R.dimen.card_image_max_height).toInt()
itemImage.scaleType = ScaleType.CENTER_CROP mView.itemImage.scaleType = ScaleType.CENTER_CROP
} }
saveBtn.setOnLikeListener(object : OnLikeListener { mView.favButton.setOnLikeListener(object : OnLikeListener {
override fun liked(likeButton: LikeButton) { override fun liked(likeButton: LikeButton) {
val (id) = items[adapterPosition] val (id) = items[adapterPosition]
api.starrItem(id).enqueue(object : Callback<SuccessResponse> { api.starrItem(id).enqueue(object : Callback<SuccessResponse> {
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {} override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {}
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) { override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
saveBtn.isLiked = false mView.favButton.isLiked = false
Toast.makeText(c, R.string.cant_mark_favortie, Toast.LENGTH_SHORT).show() Toast.makeText(c, R.string.cant_mark_favortie, Toast.LENGTH_SHORT).show()
} }
}) })
@ -191,19 +191,19 @@ class ItemCardAdapter(private val app: Activity,
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {} override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {}
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) { override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
saveBtn.isLiked = true mView.favButton.isLiked = true
Toast.makeText(c, R.string.cant_unmark_favortie, Toast.LENGTH_SHORT).show() Toast.makeText(c, R.string.cant_unmark_favortie, Toast.LENGTH_SHORT).show()
} }
}) })
} }
}) })
shareBtn.setOnClickListener { mView.shareBtn.setOnClickListener {
c.shareLink(items[adapterPosition].getLinkDecoded()) c.shareLink(items[adapterPosition].getLinkDecoded())
} }
browserBtn.setOnClickListener { mView.browserBtn.setOnClickListener {
c.openInBrowser(items[adapterPosition]) c.openInBrowserAsNewTask(items[adapterPosition])
} }
} }
@ -213,10 +213,14 @@ class ItemCardAdapter(private val app: Activity,
mView.setOnClickListener { mView.setOnClickListener {
c.openItemUrl(items[adapterPosition].getLinkDecoded(), c.openItemUrl(items[adapterPosition].getLinkDecoded(),
customTabsIntent, items[adapterPosition].content,
internalBrowser, items[adapterPosition].getThumbnail(c),
articleViewer, items[adapterPosition].title,
app) items[adapterPosition].sourceAndDateText(),
customTabsIntent,
internalBrowser,
articleViewer,
app)
} }
} }
} }

View File

@ -3,43 +3,41 @@ package apps.amine.bou.readerforselfoss.adapters
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Color import android.graphics.Color
import android.net.Uri
import android.support.constraint.ConstraintLayout import android.support.constraint.ConstraintLayout
import android.support.design.widget.Snackbar import android.support.design.widget.Snackbar
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory
import android.support.v7.widget.RecyclerView import android.support.v7.widget.RecyclerView
import android.text.Html import android.text.Html
import android.text.format.DateUtils
import android.util.TypedValue import android.util.TypedValue
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.* import android.widget.TextView
import android.widget.Toast
import apps.amine.bou.readerforselfoss.R
import apps.amine.bou.readerforselfoss.api.selfoss.Item
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
import apps.amine.bou.readerforselfoss.utils.glide.bitmapCenterCrop
import apps.amine.bou.readerforselfoss.utils.glide.circularBitmapDrawable
import apps.amine.bou.readerforselfoss.utils.openInBrowserAsNewTask
import apps.amine.bou.readerforselfoss.utils.openItemUrl
import apps.amine.bou.readerforselfoss.utils.shareLink
import apps.amine.bou.readerforselfoss.utils.sourceAndDateText
import apps.amine.bou.readerforselfoss.utils.succeeded
import com.amulyakhare.textdrawable.TextDrawable import com.amulyakhare.textdrawable.TextDrawable
import com.amulyakhare.textdrawable.util.ColorGenerator import com.amulyakhare.textdrawable.util.ColorGenerator
import com.bumptech.glide.Glide import com.crashlytics.android.Crashlytics
import com.bumptech.glide.request.target.BitmapImageViewTarget
import com.like.LikeButton import com.like.LikeButton
import com.like.OnLikeListener import com.like.OnLikeListener
import retrofit2.Call import retrofit2.Call
import retrofit2.Callback import retrofit2.Callback
import retrofit2.Response import retrofit2.Response
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.* import java.util.*
import kotlin.collections.ArrayList
import apps.amine.bou.readerforselfoss.R import kotlinx.android.synthetic.main.list_item.view.*
import apps.amine.bou.readerforselfoss.api.selfoss.Item
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
import apps.amine.bou.readerforselfoss.themes.AppColors
import apps.amine.bou.readerforselfoss.utils.*
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
class ItemListAdapter(private val app: Activity, class ItemListAdapter(private val app: Activity,
private val items: ArrayList<Item>, private val items: ArrayList<Item>,
@ -47,7 +45,9 @@ class ItemListAdapter(private val app: Activity,
private val helper: CustomTabActivityHelper, private val helper: CustomTabActivityHelper,
private val clickBehavior: Boolean, private val clickBehavior: Boolean,
private val internalBrowser: Boolean, private val internalBrowser: Boolean,
private val articleViewer: Boolean) : RecyclerView.Adapter<ItemListAdapter.ViewHolder>() { private val articleViewer: Boolean,
val debugReadingItems: Boolean,
val userIdentifier: String) : RecyclerView.Adapter<ItemListAdapter.ViewHolder>() {
private val generator: ColorGenerator = ColorGenerator.MATERIAL private val generator: ColorGenerator = ColorGenerator.MATERIAL
private val c: Context = app.baseContext private val c: Context = app.baseContext
private val bars: ArrayList<Boolean> = ArrayList(Collections.nCopies(items.size + 1, false)) private val bars: ArrayList<Boolean> = ArrayList(Collections.nCopies(items.size + 1, false))
@ -61,27 +61,27 @@ class ItemListAdapter(private val app: Activity,
val itm = items[position] val itm = items[position]
holder.saveBtn.isLiked = itm.starred holder.mView.favButton.isLiked = itm.starred
holder.title.text = Html.fromHtml(itm.title) holder.mView.title.text = Html.fromHtml(itm.title)
holder.sourceTitleAndDate.text = itm.sourceAndDateText() holder.mView.sourceTitleAndDate.text = itm.sourceAndDateText()
if (itm.getThumbnail(c).isEmpty()) { if (itm.getThumbnail(c).isEmpty()) {
val sizeInInt = 46 val sizeInInt = 46
val sizeInDp = TypedValue.applyDimension( val sizeInDp = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, sizeInInt.toFloat(), c.resources TypedValue.COMPLEX_UNIT_DIP, sizeInInt.toFloat(), c.resources
.displayMetrics).toInt() .displayMetrics).toInt()
val marginInInt = 16 val marginInInt = 16
val marginInDp = TypedValue.applyDimension( val marginInDp = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, marginInInt.toFloat(), c.resources TypedValue.COMPLEX_UNIT_DIP, marginInInt.toFloat(), c.resources
.displayMetrics).toInt() .displayMetrics).toInt()
val params = holder.sourceImage.layoutParams as ViewGroup.MarginLayoutParams val params = holder.mView.itemImage.layoutParams as ViewGroup.MarginLayoutParams
params.height = sizeInDp params.height = sizeInDp
params.width = sizeInDp params.width = sizeInDp
params.setMargins(marginInDp, 0, 0, 0) params.setMargins(marginInDp, 0, 0, 0)
holder.sourceImage.layoutParams = params holder.mView.itemImage.layoutParams = params
if (itm.getIcon(c).isEmpty()) { if (itm.getIcon(c).isEmpty()) {
val color = generator.getColor(itm.sourcetitle) val color = generator.getColor(itm.sourcetitle)
@ -93,17 +93,17 @@ class ItemListAdapter(private val app: Activity,
val builder = TextDrawable.builder().round() val builder = TextDrawable.builder().round()
val drawable = builder.build(textDrawable.toString(), color) val drawable = builder.build(textDrawable.toString(), color)
holder.sourceImage.setImageDrawable(drawable) holder.mView.itemImage.setImageDrawable(drawable)
} else { } else {
c.circularBitmapDrawable(itm.getIcon(c), holder.sourceImage) c.circularBitmapDrawable(itm.getIcon(c), holder.mView.itemImage)
} }
} else { } else {
c.bitmapCenterCrop(itm.getThumbnail(c), holder.sourceImage) c.bitmapCenterCrop(itm.getThumbnail(c), holder.mView.itemImage)
} }
if (bars[position]) holder.actionBar.visibility = View.VISIBLE else holder.actionBar.visibility = View.GONE if (bars[position]) holder.mView.actionBar.visibility = View.VISIBLE else holder.mView.actionBar.visibility = View.GONE
holder.saveBtn.isLiked = itm.starred holder.mView.favButton.isLiked = itm.starred
} }
override fun getItemCount(): Int = items.size override fun getItemCount(): Int = items.size
@ -142,11 +142,31 @@ class ItemListAdapter(private val app: Activity,
api.markItem(i.id).enqueue(object : Callback<SuccessResponse> { api.markItem(i.id).enqueue(object : Callback<SuccessResponse> {
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) { override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {
if (!response.succeeded() && debugReadingItems) {
val message =
"message: ${response.message()} " +
"response isSuccess: ${response.isSuccessful} " +
"response code: ${response.code()} " +
"response message: ${response.message()} " +
"response errorBody: ${response.errorBody()?.string()} " +
"body success: ${response.body()?.success} " +
"body isSuccess: ${response.body()?.isSuccess}"
Crashlytics.setUserIdentifier(userIdentifier)
Crashlytics.log(100, "READ_DEBUG_SUCCESS", message)
Crashlytics.logException(Exception("Was success, but did it work ?"))
Toast.makeText(c, message, Toast.LENGTH_LONG).show()
}
doUnmark(i, position) doUnmark(i, position)
} }
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) { override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
if (debugReadingItems) {
Crashlytics.setUserIdentifier(userIdentifier)
Crashlytics.log(100, "READ_DEBUG_ERROR", t.message)
Crashlytics.logException(t)
Toast.makeText(c, t.message, Toast.LENGTH_LONG).show()
}
Toast.makeText(app, app.getString(R.string.cant_mark_read), Toast.LENGTH_SHORT).show() Toast.makeText(app, app.getString(R.string.cant_mark_read), Toast.LENGTH_SHORT).show()
items.add(i) items.add(i)
notifyItemInserted(position) notifyItemInserted(position)
@ -156,13 +176,6 @@ class ItemListAdapter(private val app: Activity,
} }
inner class ViewHolder(val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) { inner class ViewHolder(val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) {
lateinit var saveBtn: LikeButton
lateinit var browserBtn: ImageButton
lateinit var shareBtn: ImageButton
lateinit var actionBar: RelativeLayout
lateinit var sourceImage: ImageView
lateinit var title: TextView
lateinit var sourceTitleAndDate: TextView
init { init {
handleClickListeners() handleClickListeners()
@ -170,23 +183,15 @@ class ItemListAdapter(private val app: Activity,
} }
private fun handleClickListeners() { private fun handleClickListeners() {
actionBar = mView.findViewById(R.id.actionBar)
sourceImage = mView.findViewById(R.id.itemImage)
title = mView.findViewById(R.id.title)
sourceTitleAndDate = mView.findViewById(R.id.sourceTitleAndDate)
saveBtn = mView.findViewById(R.id.favButton)
shareBtn = mView.findViewById(R.id.shareBtn)
browserBtn = mView.findViewById(R.id.browserBtn)
mView.favButton.setOnLikeListener(object : OnLikeListener {
saveBtn.setOnLikeListener(object : OnLikeListener {
override fun liked(likeButton: LikeButton) { override fun liked(likeButton: LikeButton) {
val (id) = items[adapterPosition] val (id) = items[adapterPosition]
api.starrItem(id).enqueue(object : Callback<SuccessResponse> { api.starrItem(id).enqueue(object : Callback<SuccessResponse> {
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {} override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {}
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) { override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
saveBtn.isLiked = false mView.favButton.isLiked = false
Toast.makeText(c, R.string.cant_mark_favortie, Toast.LENGTH_SHORT).show() Toast.makeText(c, R.string.cant_mark_favortie, Toast.LENGTH_SHORT).show()
} }
}) })
@ -198,19 +203,19 @@ class ItemListAdapter(private val app: Activity,
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {} override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {}
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) { override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
saveBtn.isLiked = true mView.favButton.isLiked = true
Toast.makeText(c, R.string.cant_unmark_favortie, Toast.LENGTH_SHORT).show() Toast.makeText(c, R.string.cant_unmark_favortie, Toast.LENGTH_SHORT).show()
} }
}) })
} }
}) })
shareBtn.setOnClickListener { mView.shareBtn.setOnClickListener {
c.shareLink(items[adapterPosition].getLinkDecoded()) c.shareLink(items[adapterPosition].getLinkDecoded())
} }
browserBtn.setOnClickListener { mView.browserBtn.setOnClickListener {
c.openInBrowser(items[adapterPosition]) c.openInBrowserAsNewTask(items[adapterPosition])
} }
} }
@ -224,10 +229,14 @@ class ItemListAdapter(private val app: Activity,
if (!clickBehavior) { if (!clickBehavior) {
mView.setOnClickListener { mView.setOnClickListener {
c.openItemUrl(items[adapterPosition].getLinkDecoded(), c.openItemUrl(items[adapterPosition].getLinkDecoded(),
customTabsIntent, items[adapterPosition].content,
internalBrowser, items[adapterPosition].getThumbnail(c),
articleViewer, items[adapterPosition].title,
app) items[adapterPosition].sourceAndDateText(),
customTabsIntent,
internalBrowser,
articleViewer,
app)
} }
mView.setOnLongClickListener { mView.setOnLongClickListener {
actionBarShowHide() actionBarShowHide()
@ -237,10 +246,14 @@ class ItemListAdapter(private val app: Activity,
mView.setOnClickListener { actionBarShowHide() } mView.setOnClickListener { actionBarShowHide() }
mView.setOnLongClickListener { mView.setOnLongClickListener {
c.openItemUrl(items[adapterPosition].getLinkDecoded(), c.openItemUrl(items[adapterPosition].getLinkDecoded(),
customTabsIntent, items[adapterPosition].content,
internalBrowser, items[adapterPosition].getThumbnail(c),
articleViewer, items[adapterPosition].title,
app) items[adapterPosition].sourceAndDateText(),
customTabsIntent,
internalBrowser,
articleViewer,
app)
true true
} }
} }
@ -248,7 +261,11 @@ class ItemListAdapter(private val app: Activity,
private fun actionBarShowHide() { private fun actionBarShowHide() {
bars[adapterPosition] = true bars[adapterPosition] = true
if (actionBar.visibility == View.GONE) actionBar.visibility = View.VISIBLE else actionBar.visibility = View.GONE if (mView.actionBar.visibility == View.GONE) {
mView.actionBar.visibility = View.VISIBLE
} else {
mView.actionBar.visibility = View.GONE
}
} }
} }
} }

View File

@ -10,19 +10,18 @@ import android.widget.Button
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import apps.amine.bou.readerforselfoss.R
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
import apps.amine.bou.readerforselfoss.api.selfoss.Sources
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
import apps.amine.bou.readerforselfoss.utils.glide.circularBitmapDrawable
import apps.amine.bou.readerforselfoss.utils.toTextDrawableString
import com.amulyakhare.textdrawable.TextDrawable import com.amulyakhare.textdrawable.TextDrawable
import com.amulyakhare.textdrawable.util.ColorGenerator import com.amulyakhare.textdrawable.util.ColorGenerator
import retrofit2.Call import retrofit2.Call
import retrofit2.Callback import retrofit2.Callback
import retrofit2.Response import retrofit2.Response
import kotlinx.android.synthetic.main.source_list_item.view.*
import apps.amine.bou.readerforselfoss.R
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
import apps.amine.bou.readerforselfoss.api.selfoss.Sources
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
import apps.amine.bou.readerforselfoss.utils.circularBitmapDrawable
import apps.amine.bou.readerforselfoss.utils.toTextDrawableString
class SourcesListAdapter(private val app: Activity, class SourcesListAdapter(private val app: Activity,
@ -43,13 +42,13 @@ class SourcesListAdapter(private val app: Activity,
val color = generator.getColor(itm.title) val color = generator.getColor(itm.title)
val drawable = val drawable =
TextDrawable TextDrawable
.builder() .builder()
.round() .round()
.build(itm.title.toTextDrawableString(), color) .build(itm.title.toTextDrawableString(), color)
holder.sourceImage.setImageDrawable(drawable) holder.itemImage.setImageDrawable(drawable)
} else { } else {
c.circularBitmapDrawable(itm.getIcon(c), holder.sourceImage) c.circularBitmapDrawable(itm.getIcon(c), holder.itemImage)
} }
holder.sourceTitle.text = itm.title holder.sourceTitle.text = itm.title
@ -60,17 +59,14 @@ class SourcesListAdapter(private val app: Activity,
} }
inner class ViewHolder(internal val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) { inner class ViewHolder(internal val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) {
lateinit var sourceImage: ImageView lateinit var itemImage: ImageView
lateinit var sourceTitle: TextView lateinit var sourceTitle: TextView
init { init {
handleClickListeners() handleClickListeners()
} }
private fun handleClickListeners() { private fun handleClickListeners() {
sourceImage = mView.findViewById(R.id.itemImage)
sourceTitle = mView.findViewById(R.id.sourceTitle)
val deleteBtn: Button = mView.findViewById(R.id.deleteBtn) val deleteBtn: Button = mView.findViewById(R.id.deleteBtn)

View File

@ -9,13 +9,16 @@ import retrofit2.converter.gson.GsonConverterFactory
class MercuryApi(private val key: String) { class MercuryApi(private val key: String, shouldLog: Boolean) {
private val service: MercuryService private val service: MercuryService
init { init {
val interceptor = HttpLoggingInterceptor() val interceptor = HttpLoggingInterceptor()
interceptor.level = HttpLoggingInterceptor.Level.BODY interceptor.level = if (shouldLog)
HttpLoggingInterceptor.Level.BODY
else
HttpLoggingInterceptor.Level.NONE
val client = OkHttpClient.Builder().addInterceptor(interceptor).build() val client = OkHttpClient.Builder().addInterceptor(interceptor).build()
val gson = GsonBuilder() val gson = GsonBuilder()

View File

@ -2,20 +2,20 @@ package apps.amine.bou.readerforselfoss.api.mercury
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import com.google.gson.annotations.SerializedName
class ParsedContent(@SerializedName("title") val title: String,
class ParsedContent(val title: String, @SerializedName("content") val content: String,
val content: String, @SerializedName("date_published") val date_published: String,
val date_published: String, @SerializedName("lead_image_url") val lead_image_url: String,
val lead_image_url: String, @SerializedName("dek") val dek: String,
val dek: String, @SerializedName("url") val url: String,
val url: String, @SerializedName("domain") val domain: String,
val domain: String, @SerializedName("excerpt") val excerpt: String,
val excerpt: String, @SerializedName("total_pages") val total_pages: Int,
val total_pages: Int, @SerializedName("rendered_pages") val rendered_pages: Int,
val rendered_pages: Int, @SerializedName("next_page_url") val next_page_url: String) : Parcelable {
val next_page_url: String) : Parcelable {
companion object { companion object {
@JvmField val CREATOR: Parcelable.Creator<ParsedContent> = object : Parcelable.Creator<ParsedContent> { @JvmField val CREATOR: Parcelable.Creator<ParsedContent> = object : Parcelable.Creator<ParsedContent> {

View File

@ -1,12 +1,10 @@
package apps.amine.bou.readerforselfoss.api.selfoss package apps.amine.bou.readerforselfoss.api.selfoss
import java.lang.reflect.Type
import com.google.gson.JsonParseException
import com.google.gson.JsonDeserializationContext import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonElement
import com.google.gson.JsonDeserializer import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonParseException
import java.lang.reflect.Type
internal class BooleanTypeAdapter : JsonDeserializer<Boolean> { internal class BooleanTypeAdapter : JsonDeserializer<Boolean> {

View File

@ -2,13 +2,8 @@ package apps.amine.bou.readerforselfoss.api.selfoss
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import apps.amine.bou.readerforselfoss.utils.Config
import android.support.v7.app.AlertDialog import apps.amine.bou.readerforselfoss.utils.getUnsafeHttpClient
import android.widget.Toast
import apps.amine.bou.readerforselfoss.LoginActivity
import apps.amine.bou.readerforselfoss.R
import java.util.concurrent.ConcurrentHashMap
import com.burgstaller.okhttp.AuthenticationCacheInterceptor import com.burgstaller.okhttp.AuthenticationCacheInterceptor
import com.burgstaller.okhttp.CachingAuthenticatorDecorator import com.burgstaller.okhttp.CachingAuthenticatorDecorator
import com.burgstaller.okhttp.DispatchingAuthenticator import com.burgstaller.okhttp.DispatchingAuthenticator
@ -18,34 +13,40 @@ import com.burgstaller.okhttp.digest.Credentials
import com.burgstaller.okhttp.digest.DigestAuthenticator import com.burgstaller.okhttp.digest.DigestAuthenticator
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Call import retrofit2.Call
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.ConcurrentHashMap
import apps.amine.bou.readerforselfoss.utils.Config
// codebeat:disable[ARITY,TOO_MANY_FUNCTIONS] class SelfossApi(c: Context, callingActivity: Activity, isWithSelfSignedCert: Boolean, shouldLog: Boolean) {
class SelfossApi(c: Context, callingActivity: Activity) {
private lateinit var service: SelfossService private lateinit var service: SelfossService
private val config: Config = Config(c) private val config: Config = Config(c)
private val userName: String private val userName: String
private val password: String private val password: String
fun OkHttpClient.Builder.maybeWithSelfSigned(isWithSelfSignedCert: Boolean): OkHttpClient.Builder =
if (isWithSelfSignedCert) {
getUnsafeHttpClient()
} else {
this
}
fun Credentials.createAuthenticator(): DispatchingAuthenticator = fun Credentials.createAuthenticator(): DispatchingAuthenticator =
DispatchingAuthenticator.Builder() DispatchingAuthenticator.Builder()
.with("digest", DigestAuthenticator(this)) .with("digest", DigestAuthenticator(this))
.with("basic", BasicAuthenticator(this)) .with("basic", BasicAuthenticator(this))
.build() .build()
fun DispatchingAuthenticator.getHttpClien(): OkHttpClient { fun DispatchingAuthenticator.getHttpClien(isWithSelfSignedCert: Boolean): OkHttpClient.Builder {
val authCache = ConcurrentHashMap<String, CachingAuthenticator>() val authCache = ConcurrentHashMap<String, CachingAuthenticator>()
return OkHttpClient return OkHttpClient
.Builder() .Builder()
.maybeWithSelfSigned(isWithSelfSignedCert)
.authenticator(CachingAuthenticatorDecorator(this, authCache)) .authenticator(CachingAuthenticatorDecorator(this, authCache))
.addInterceptor(AuthenticationCacheInterceptor(authCache)) .addInterceptor(AuthenticationCacheInterceptor(authCache))
.build()
} }
@ -65,13 +66,23 @@ class SelfossApi(c: Context, callingActivity: Activity) {
.setLenient() .setLenient()
.create() .create()
val logging = HttpLoggingInterceptor()
logging.level = if (shouldLog)
HttpLoggingInterceptor.Level.BODY
else
HttpLoggingInterceptor.Level.NONE
val httpClient = authenticator.getHttpClien(isWithSelfSignedCert)
httpClient.addInterceptor(logging)
try { try {
val retrofit = val retrofit =
Retrofit Retrofit
.Builder() .Builder()
.baseUrl(config.baseUrl) .baseUrl(config.baseUrl)
.client(authenticator.getHttpClien()) .client(httpClient.build())
.addConverterFactory(GsonConverterFactory.create(gson)) .addConverterFactory(GsonConverterFactory.create(gson))
.build() .build()
service = retrofit.create(SelfossService::class.java) service = retrofit.create(SelfossService::class.java)
@ -83,17 +94,17 @@ class SelfossApi(c: Context, callingActivity: Activity) {
fun login(): Call<SuccessResponse> = fun login(): Call<SuccessResponse> =
service.loginToSelfoss(config.userLogin, config.userPassword) service.loginToSelfoss(config.userLogin, config.userPassword)
fun readItems(tag: String?, sourceId: Long?, search: String?): Call<List<Item>> = fun readItems(tag: String?, sourceId: Long?, search: String?, itemsNumber: Int, offset: Int): Call<List<Item>> =
getItems("read", tag, sourceId, search) getItems("read", tag, sourceId, search, itemsNumber, offset)
fun newItems(tag: String?, sourceId: Long?, search: String?): Call<List<Item>> = fun newItems(tag: String?, sourceId: Long?, search: String?, itemsNumber: Int, offset: Int): Call<List<Item>> =
getItems("unread", tag, sourceId, search) getItems("unread", tag, sourceId, search, itemsNumber, offset)
fun starredItems(tag: String?, sourceId: Long?, search: String?): Call<List<Item>> = fun starredItems(tag: String?, sourceId: Long?, search: String?, itemsNumber: Int, offset: Int): Call<List<Item>> =
getItems("starred", tag, sourceId, search) getItems("starred", tag, sourceId, search, itemsNumber, offset)
private fun getItems(type: String, tag: String?, sourceId: Long?, search: String?): Call<List<Item>> = private fun getItems(type: String, tag: String?, sourceId: Long?, search: String?, items: Int, offset: Int): Call<List<Item>> =
service.getItems(type, tag, sourceId, search, userName, password) service.getItems(type, tag, sourceId, search, userName, password, items, offset)
fun markItem(itemId: String): Call<SuccessResponse> = fun markItem(itemId: String): Call<SuccessResponse> =
service.markAsRead(itemId, userName, password) service.markAsRead(itemId, userName, password)
@ -132,5 +143,3 @@ class SelfossApi(c: Context, callingActivity: Activity) {
service.createSource(title, url, spout, tags, filter, userName, password) service.createSource(title, url, spout, tags, filter, userName, password)
} }
// codebeat:enable[ARITY,TOO_MANY_FUNCTIONS]

View File

@ -7,7 +7,7 @@ import android.os.Parcelable
import apps.amine.bou.readerforselfoss.utils.Config import apps.amine.bou.readerforselfoss.utils.Config
import apps.amine.bou.readerforselfoss.utils.isEmptyOrNullOrNullString import apps.amine.bou.readerforselfoss.utils.isEmptyOrNullOrNullString
import com.google.gson.annotations.SerializedName
private fun constructUrl(config: Config?, path: String, file: String): String { private fun constructUrl(config: Config?, path: String, file: String): String {
@ -19,23 +19,28 @@ private fun constructUrl(config: Config?, path: String, file: String): String {
} }
data class Tag(val tag: String, val color: String, val unread: Int) data class Tag(@SerializedName("tag") val tag: String,
@SerializedName("color") val color: String,
@SerializedName("unread") val unread: Int)
class SuccessResponse(val success: Boolean) { class SuccessResponse(@SerializedName("success") val success: Boolean) {
val isSuccess: Boolean val isSuccess: Boolean
get() = success get() = success
} }
class Stats(val total: Int, val unread: Int, val starred: Int) class Stats(@SerializedName("total") val total: Int,
@SerializedName("unread") val unread: Int,
@SerializedName("starred") val starred: Int)
data class Spout(val name: String, val description: String) data class Spout(@SerializedName("name") val name: String,
@SerializedName("description") val description: String)
data class Sources(val id: String, data class Sources(@SerializedName("id") val id: String,
val title: String, @SerializedName("title") val title: String,
val tags: String, @SerializedName("tags") val tags: String,
val spout: String, @SerializedName("spout") val spout: String,
val error: String, @SerializedName("error") val error: String,
val icon: String) { @SerializedName("icon") val icon: String) {
var config: Config? = null var config: Config? = null
fun getIcon(app: Context): String { fun getIcon(app: Context): String {
@ -47,15 +52,16 @@ data class Sources(val id: String,
} }
data class Item(val id: String, data class Item(@SerializedName("id") val id: String,
val datetime: String, @SerializedName("datetime") val datetime: String,
val title: String, @SerializedName("title") val title: String,
val unread: Boolean, @SerializedName("content") val content: String,
val starred: Boolean, @SerializedName("unread") val unread: Boolean,
val thumbnail: String, @SerializedName("starred") val starred: Boolean,
val icon: String, @SerializedName("thumbnail") val thumbnail: String,
val link: String, @SerializedName("icon") val icon: String,
val sourcetitle: String) : Parcelable { @SerializedName("link") val link: String,
@SerializedName("sourcetitle") val sourcetitle: String) : Parcelable {
var config: Config? = null var config: Config? = null
@ -67,15 +73,16 @@ data class Item(val id: String,
} }
constructor(source: Parcel) : this( constructor(source: Parcel) : this(
id = source.readString(), id = source.readString(),
datetime = source.readString(), datetime = source.readString(),
title = source.readString(), title = source.readString(),
unread = 0.toByte() != source.readByte(), content = source.readString(),
starred = 0.toByte() != source.readByte(), unread = 0.toByte() != source.readByte(),
thumbnail = source.readString(), starred = 0.toByte() != source.readByte(),
icon = source.readString(), thumbnail = source.readString(),
link = source.readString(), icon = source.readString(),
sourcetitle = source.readString() link = source.readString(),
sourcetitle = source.readString()
) )
override fun describeContents() = 0 override fun describeContents() = 0
@ -84,6 +91,7 @@ data class Item(val id: String,
dest.writeString(id) dest.writeString(id)
dest.writeString(datetime) dest.writeString(datetime)
dest.writeString(title) dest.writeString(title)
dest.writeString(content)
dest.writeByte((if (unread) 1 else 0)) dest.writeByte((if (unread) 1 else 0))
dest.writeByte((if (starred) 1 else 0)) dest.writeByte((if (starred) 1 else 0))
dest.writeString(thumbnail) dest.writeString(thumbnail)
@ -109,20 +117,26 @@ data class Item(val id: String,
// TODO: maybe find a better way to handle these kind of urls // TODO: maybe find a better way to handle these kind of urls
fun getLinkDecoded(): String { fun getLinkDecoded(): String {
var stringUrl: String var stringUrl: String
if (link.startsWith("http://news.google.com/news/") || link.startsWith("https://news.google.com/news/")) { stringUrl = if (link.startsWith("http://news.google.com/news/") || link.startsWith("https://news.google.com/news/")) {
if (link.contains("&amp;url=")) { if (link.contains("&amp;url=")) {
stringUrl = link.substringAfter("&amp;url=") link.substringAfter("&amp;url=")
} else { } else {
stringUrl = this.link.replace("&amp;", "&") this.link.replace("&amp;", "&")
} }
} else { } else {
stringUrl = this.link.replace("&amp;", "&") this.link.replace("&amp;", "&")
} }
// handle :443 => https // handle :443 => https
if (stringUrl.contains(":443")) { if (stringUrl.contains(":443")) {
stringUrl = stringUrl.replace(":443", "").replace("http://", "https://") stringUrl = stringUrl.replace(":443", "").replace("http://", "https://")
} }
// handle url not starting with http
if (stringUrl.startsWith("//")) {
stringUrl = "http:" + stringUrl
}
return stringUrl return stringUrl
} }

View File

@ -5,36 +5,43 @@ import retrofit2.http.DELETE
import retrofit2.http.Field import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded import retrofit2.http.FormUrlEncoded
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Headers
import retrofit2.http.POST import retrofit2.http.POST
import retrofit2.http.Path import retrofit2.http.Path
import retrofit2.http.Query import retrofit2.http.Query
// codebeat:disable[ARITY]
internal interface SelfossService { internal interface SelfossService {
@GET("login") @GET("login")
fun loginToSelfoss(@Query("username") username: String, @Query("password") password: String): Call<SuccessResponse> fun loginToSelfoss(@Query("username") username: String, @Query("password") password: String): Call<SuccessResponse>
@GET("items") @GET("items")
fun getItems(@Query("type") type: String, fun getItems(@Query("type") type: String,
@Query("tag") tag: String?, @Query("tag") tag: String?,
@Query("source") source: Long?, @Query("source") source: Long?,
@Query("search") search: String?, @Query("search") search: String?,
@Query("username") username: String, @Query("username") username: String,
@Query("password") password: String): Call<List<Item>> @Query("password") password: String,
@Query("items") items: Int,
@Query("offset") offset: Int): Call<List<Item>>
@Headers("Content-Type: application/x-www-form-urlencoded")
@POST("mark/{id}") @POST("mark/{id}")
fun markAsRead(@Path("id") id: String, fun markAsRead(@Path("id") id: String,
@Query("username") username: String, @Query("username") username: String,
@Query("password") password: String): Call<SuccessResponse> @Query("password") password: String): Call<SuccessResponse>
@Headers("Content-Type: application/x-www-form-urlencoded")
@POST("unmark/{id}") @POST("unmark/{id}")
fun unmarkAsRead(@Path("id") id: String, fun unmarkAsRead(@Path("id") id: String,
@Query("username") username: String, @Query("username") username: String,
@Query("password") password: String): Call<SuccessResponse> @Query("password") password: String): Call<SuccessResponse>
@FormUrlEncoded @FormUrlEncoded
@POST("mark") @POST("mark")
fun markAllAsRead(@Field("ids[]") ids: List<String>, fun markAllAsRead(@Field("ids[]") ids: List<String>,
@ -42,12 +49,14 @@ internal interface SelfossService {
@Query("password") password: String): Call<SuccessResponse> @Query("password") password: String): Call<SuccessResponse>
@Headers("Content-Type: application/x-www-form-urlencoded")
@POST("starr/{id}") @POST("starr/{id}")
fun starr(@Path("id") id: String, fun starr(@Path("id") id: String,
@Query("username") username: String, @Query("username") username: String,
@Query("password") password: String): Call<SuccessResponse> @Query("password") password: String): Call<SuccessResponse>
@Headers("Content-Type: application/x-www-form-urlencoded")
@POST("unstarr/{id}") @POST("unstarr/{id}")
fun unstarr(@Path("id") id: String, fun unstarr(@Path("id") id: String,
@Query("username") username: String, @Query("username") username: String,
@ -68,10 +77,12 @@ internal interface SelfossService {
fun update(@Query("username") username: String, fun update(@Query("username") username: String,
@Query("password") password: String): Call<String> @Query("password") password: String): Call<String>
@GET("sources/spouts") @GET("sources/spouts")
fun spouts(@Query("username") username: String, fun spouts(@Query("username") username: String,
@Query("password") password: String): Call<Map<String, Spout>> @Query("password") password: String): Call<Map<String, Spout>>
@GET("sources/list") @GET("sources/list")
fun sources(@Query("username") username: String, fun sources(@Query("username") username: String,
@Query("password") password: String): Call<List<Sources>> @Query("password") password: String): Call<List<Sources>>
@ -82,6 +93,7 @@ internal interface SelfossService {
@Query("username") username: String, @Query("username") username: String,
@Query("password") password: String): Call<SuccessResponse> @Query("password") password: String): Call<SuccessResponse>
@FormUrlEncoded @FormUrlEncoded
@POST("source") @POST("source")
fun createSource(@Field("title") title: String, fun createSource(@Field("title") title: String,
@ -92,4 +104,3 @@ internal interface SelfossService {
@Query("username") username: String, @Query("username") username: String,
@Query("password") password: String): Call<SuccessResponse> @Query("password") password: String): Call<SuccessResponse>
} }
// codebeat:disable[ARITY]

View File

@ -24,7 +24,7 @@ import com.ftinc.scoop.Scoop;
* A {@link PreferenceActivity} which implements and proxies the necessary calls * A {@link PreferenceActivity} which implements and proxies the necessary calls
* to be used with AppCompat. * to be used with AppCompat.
*/ */
public abstract class AppCompatPreferenceActivity extends PreferenceActivity { public abstract class AppCompatPreferenceActivity extends PreferenceActivity { //NOSONAR
private AppCompatDelegate mDelegate; private AppCompatDelegate mDelegate;
@ -116,6 +116,7 @@ public abstract class AppCompatPreferenceActivity extends PreferenceActivity {
getDelegate().onDestroy(); getDelegate().onDestroy();
} }
@Override
public void invalidateOptionsMenu() { public void invalidateOptionsMenu() {
getDelegate().invalidateOptionsMenu(); getDelegate().invalidateOptionsMenu();
} }

View File

@ -1,25 +1,35 @@
package apps.amine.bou.readerforselfoss.settings; package apps.amine.bou.readerforselfoss.settings;
import java.util.List;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.preference.EditTextPreference;
import android.preference.Preference; import android.preference.Preference;
import android.preference.Preference.OnPreferenceChangeListener; import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceActivity; import android.preference.PreferenceActivity;
import android.preference.SwitchPreference;
import android.support.v7.app.ActionBar;
import android.preference.PreferenceFragment; import android.preference.PreferenceFragment;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.preference.SwitchPreference;
import android.support.v7.app.ActionBar;
import android.text.InputFilter;
import android.text.Spanned;
import android.view.MenuItem; import android.view.MenuItem;
import android.widget.Toast;
import java.util.List; import apps.amine.bou.readerforselfoss.BuildConfig;
import apps.amine.bou.readerforselfoss.R; import apps.amine.bou.readerforselfoss.R;
import apps.amine.bou.readerforselfoss.utils.Config;
import com.ftinc.scoop.ui.ScoopSettingsActivity; import com.ftinc.scoop.ui.ScoopSettingsActivity;
@ -34,7 +44,7 @@ import com.ftinc.scoop.ui.ScoopSettingsActivity;
* href="http://developer.android.com/guide/topics/ui/settings.html">Settings * href="http://developer.android.com/guide/topics/ui/settings.html">Settings
* API Guide</a> for more information on developing a Settings UI. * API Guide</a> for more information on developing a Settings UI.
*/ */
public class SettingsActivity extends AppCompatPreferenceActivity { public class SettingsActivity extends AppCompatPreferenceActivity { //NOSONAR
/** /**
* A preference value change listener that updates the preference's summary * A preference value change listener that updates the preference's summary
* to reflect its new value. * to reflect its new value.
@ -116,9 +126,11 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
* This method stops fragment injection in malicious applications. * This method stops fragment injection in malicious applications.
* Make sure to deny any unknown fragments here. * Make sure to deny any unknown fragments here.
*/ */
@Override
protected boolean isValidFragment(String fragmentName) { protected boolean isValidFragment(String fragmentName) {
return PreferenceFragment.class.getName().equals(fragmentName) return PreferenceFragment.class.getName().equals(fragmentName)
|| GeneralPreferenceFragment.class.getName().equals(fragmentName) || GeneralPreferenceFragment.class.getName().equals(fragmentName)
|| DebugPreferenceFragment.class.getName().equals(fragmentName)
|| LinksPreferenceFragment.class.getName().equals(fragmentName); || LinksPreferenceFragment.class.getName().equals(fragmentName);
} }
@ -144,6 +156,63 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
return true; return true;
} }
}); });
EditTextPreference itemsNumber = (EditTextPreference) findPreference("prefer_api_items_number");
itemsNumber.getEditText().setFilters(new InputFilter[]{
new InputFilter (){
@Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
try {
int input = Integer.parseInt(dest.toString() + source.toString());
if (input <= 200 && input >0)
return null;
} catch (NumberFormatException nfe) {
Toast.makeText(getActivity(), R.string.items_number_should_be_number, Toast.LENGTH_LONG).show();
}
return "";
}
}
});
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
getActivity().finish();
return true;
}
return super.onOptionsItemSelected(item);
}
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static class DebugPreferenceFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.pref_debug);
setHasOptionsMenu(true);
SharedPreferences pref = getActivity().getSharedPreferences(Config.Companion.getSettingsName(), Context.MODE_PRIVATE);
final String id = pref.getString("unique_id", "...");
final Preference identifier = findPreference("debug_identifier");
final ClipboardManager clipboard = (ClipboardManager)
getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
identifier.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
ClipData clip = ClipData.newPlainText("Selfoss unique id", id);
clipboard.setPrimaryClip(clip);
Toast.makeText(getActivity(), R.string.unique_id_to_clipboard, Toast.LENGTH_LONG).show();
return true;
}
});
identifier.setTitle(id);
} }
@Override @Override
@ -177,7 +246,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
findPreference( "trackerLink" ).setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { findPreference( "trackerLink" ).setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override @Override
public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) {
openUrl(Uri.parse(getString(R.string.tracker_url))); openUrl(Uri.parse(BuildConfig.TRACKER_URL));
return true; return true;
} }
}); });
@ -185,7 +254,15 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
findPreference("sourceLink").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { findPreference("sourceLink").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override @Override
public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) {
openUrl(Uri.parse(getString(R.string.source_url))); openUrl(Uri.parse(BuildConfig.SOURCE_URL));
return false;
}
});
findPreference("translation").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
openUrl(Uri.parse(BuildConfig.TRANSLATION_URL));
return false; return false;
} }
}); });
@ -206,7 +283,9 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
public void onHeaderClick(Header header, int position) { public void onHeaderClick(Header header, int position) {
super.onHeaderClick(header, position); super.onHeaderClick(header, position);
if (header.id == R.id.theme_change) { if (header.id == R.id.theme_change) {
getApplicationContext().startActivity(ScoopSettingsActivity.createIntent(getApplicationContext())); Intent intent = ScoopSettingsActivity.createIntent(getApplicationContext());
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getApplicationContext().startActivity(intent);
finish(); finish();
} }
} }

View File

@ -5,8 +5,6 @@ import android.content.Context
import android.support.annotation.ColorInt import android.support.annotation.ColorInt
import android.util.TypedValue import android.util.TypedValue
import apps.amine.bou.readerforselfoss.R import apps.amine.bou.readerforselfoss.R
import java.lang.reflect.AccessibleObject.setAccessible
class AppColors(a: Activity) { class AppColors(a: Activity) {

View File

@ -0,0 +1,7 @@
package apps.amine.bou.readerforselfoss.utils
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
import retrofit2.Response
fun Response<SuccessResponse>.succeeded(): Boolean =
this.code() === 200 && this.body() != null && this.body()!!.isSuccess

View File

@ -5,48 +5,10 @@ import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.net.Uri import android.net.Uri
import android.support.v7.app.AlertDialog import android.support.v7.app.AlertDialog
import android.text.TextUtils
import android.util.Patterns
import com.google.firebase.remoteconfig.FirebaseRemoteConfig
import okhttp3.HttpUrl
import apps.amine.bou.readerforselfoss.BuildConfig
import apps.amine.bou.readerforselfoss.R import apps.amine.bou.readerforselfoss.R
import apps.amine.bou.readerforselfoss.api.selfoss.Item import com.google.firebase.remoteconfig.FirebaseRemoteConfig
fun Context.checkAndDisplayStoreApk() = {
fun isStoreVersion(): Boolean =
try {
val installer = this.packageManager
.getInstallerPackageName(this.packageName)
!TextUtils.isEmpty(installer)
} catch (e: Throwable) {
false
}
if (!isStoreVersion() && !BuildConfig.GITHUB_VERSION) {
val alertDialog = AlertDialog.Builder(this).create()
alertDialog.setTitle(getString(R.string.warning_version))
alertDialog.setMessage(getString(R.string.text_version))
alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "OK",
{ dialog, _ -> dialog.dismiss() })
alertDialog.show()
} else Unit
}
fun String.isUrlValid(): Boolean {
val baseUrl = HttpUrl.parse(this)
var existsAndEndsWithSlash = false
if (baseUrl != null) {
val pathSegments = baseUrl.pathSegments()
existsAndEndsWithSlash = "" == pathSegments[pathSegments.size - 1]
}
return Patterns.WEB_URL.matcher(this).matches() && existsAndEndsWithSlash
}
fun String?.isEmptyOrNullOrNullString(): Boolean = fun String?.isEmptyOrNullOrNullString(): Boolean =
this == null || this == "null" || this.isEmpty() this == null || this == "null" || this.isEmpty()
@ -94,7 +56,7 @@ fun String.longHash(): Long {
val l = this.length val l = this.length
val chars = this.toCharArray() val chars = this.toCharArray()
for (i in 0..l - 1) { for (i in 0 until l) {
h = 31 * h + chars[i].toLong() h = 31 * h + chars[i].toLong()
} }
return h return h
@ -113,11 +75,4 @@ fun Context.shareLink(itemUrl: String) {
sendIntent.putExtra(Intent.EXTRA_TEXT, itemUrl.toStringUriWithHttp()) sendIntent.putExtra(Intent.EXTRA_TEXT, itemUrl.toStringUriWithHttp())
sendIntent.type = "text/plain" sendIntent.type = "text/plain"
startActivity(Intent.createChooser(sendIntent, getString(R.string.share)).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)) startActivity(Intent.createChooser(sendIntent, getString(R.string.share)).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
}
fun Context.openInBrowser(i: Item) {
val intent = Intent(Intent.ACTION_VIEW)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
intent.data = Uri.parse(i.getLinkDecoded().toStringUriWithHttp())
startActivity(intent)
} }

View File

@ -1,24 +0,0 @@
package apps.amine.bou.readerforselfoss.utils
import android.content.Context
import android.graphics.Bitmap
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory
import android.widget.ImageView
import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.BitmapImageViewTarget
fun Context.bitmapCenterCrop(url: String, iv: ImageView) =
Glide.with(this).load(url).asBitmap().centerCrop().into(iv)
fun Context.bitmapFitCenter(url: String, iv: ImageView) =
Glide.with(this).load(url).asBitmap().fitCenter().into(iv)
fun Context.circularBitmapDrawable(url: String, iv: ImageView) =
Glide.with(this).load(url).asBitmap().centerCrop().into(object : BitmapImageViewTarget(iv) {
override fun setResource(resource: Bitmap) {
val circularBitmapDrawable = RoundedBitmapDrawableFactory.create(resources, resource)
circularBitmapDrawable.isCircular = true
iv.setImageDrawable(circularBitmapDrawable)
}
})

View File

@ -0,0 +1,39 @@
package apps.amine.bou.readerforselfoss.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() =
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

@ -16,7 +16,7 @@ fun String.toTextDrawableString(): String {
} }
fun Item.sourceAndDateText(): String { fun Item.sourceAndDateText(): String {
var formattedDate: String = try { val formattedDate: String = try {
" " + DateUtils.getRelativeTimeSpanString( " " + DateUtils.getRelativeTimeSpanString(
SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(this.datetime).time, SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(this.datetime).time,
Date().time, Date().time,

View File

@ -7,14 +7,14 @@ import android.content.Intent
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.net.Uri import android.net.Uri
import android.support.customtabs.CustomTabsIntent import android.support.customtabs.CustomTabsIntent
import android.util.Patterns
import xyz.klinker.android.drag_dismiss.DragDismissIntentBuilder import android.widget.Toast
import apps.amine.bou.readerforselfoss.R import apps.amine.bou.readerforselfoss.R
import apps.amine.bou.readerforselfoss.ReaderActivity import apps.amine.bou.readerforselfoss.ReaderActivity
import apps.amine.bou.readerforselfoss.api.selfoss.Item import apps.amine.bou.readerforselfoss.api.selfoss.Item
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
import okhttp3.HttpUrl
import xyz.klinker.android.drag_dismiss.DragDismissIntentBuilder
fun Context.buildCustomTabsIntent(): CustomTabsIntent { fun Context.buildCustomTabsIntent(): CustomTabsIntent {
@ -50,35 +50,61 @@ fun Context.buildCustomTabsIntent(): CustomTabsIntent {
return intentBuilder.build() return intentBuilder.build()
} }
fun Context.openItemUrlInternally(linkDecoded: String,
content: String,
image: String,
title: String,
source: String,
customTabsIntent: CustomTabsIntent,
articleViewer: Boolean,
app: Activity) {
if (articleViewer) {
val intent = Intent(this, ReaderActivity::class.java)
/*DragDismissIntentBuilder(this)
.setFullscreenOnTablets(true) // defaults to false, tablets will have padding on each side
.setDragElasticity(DragDismissIntentBuilder.DragElasticity.NORMAL) // Larger elasticities will make it easier to dismiss.
.setDrawUnderStatusBar(true)
.build(intent)*/
intent.putExtra("url", linkDecoded)
intent.putExtra("content", content)
intent.putExtra("title", title)
intent.putExtra("image", image)
intent.putExtra("source", source)
app.startActivity(intent)
} else {
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(linkDecoded: String, fun Context.openItemUrl(linkDecoded: String,
content: String,
image: String,
title: String,
source: String,
customTabsIntent: CustomTabsIntent, customTabsIntent: CustomTabsIntent,
internalBrowser: Boolean, internalBrowser: Boolean,
articleViewer: Boolean, articleViewer: Boolean,
app: Activity) { app: Activity) {
if (!internalBrowser) {
openInBrowser(linkDecoded, app) if (!linkDecoded.isUrlValid()) {
Toast.makeText(this, this.getString(R.string.cant_open_invalid_url), Toast.LENGTH_LONG).show()
} else { } else {
if (articleViewer) { if (!internalBrowser) {
val intent = Intent(this, ReaderActivity::class.java) openInBrowser(linkDecoded, app)
DragDismissIntentBuilder(this)
.setFullscreenOnTablets(true) // defaults to false, tablets will have padding on each side
.setDragElasticity(DragDismissIntentBuilder.DragElasticity.NORMAL) // Larger elasticities will make it easier to dismiss.
.build(intent)
intent.putExtra("url", linkDecoded)
app.startActivity(intent)
} else { } else {
try { this.openItemUrlInternally(linkDecoded, content, image, title, source, customTabsIntent, articleViewer, app)
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)
}
} }
} }
} }
@ -88,3 +114,24 @@ private fun openInBrowser(linkDecoded: String, app: Activity) {
intent.data = Uri.parse(linkDecoded) intent.data = Uri.parse(linkDecoded)
app.startActivity(intent) app.startActivity(intent)
} }
fun String.isUrlValid(): Boolean =
HttpUrl.parse(this) != null && Patterns.WEB_URL.matcher(this).matches()
fun String.isBaseUrlValid(): Boolean {
val baseUrl = HttpUrl.parse(this)
var existsAndEndsWithSlash = false
if (baseUrl != null) {
val pathSegments = baseUrl.pathSegments()
existsAndEndsWithSlash = "" == pathSegments[pathSegments.size - 1]
}
return Patterns.WEB_URL.matcher(this).matches() && existsAndEndsWithSlash
}
fun Context.openInBrowserAsNewTask(i: Item) {
val intent = Intent(Intent.ACTION_VIEW)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
intent.data = Uri.parse(i.getLinkDecoded().toStringUriWithHttp())
startActivity(intent)
}

View File

@ -0,0 +1,33 @@
package apps.amine.bou.readerforselfoss.utils
import android.content.Context
import android.support.design.widget.CoordinatorLayout
import android.support.design.widget.FloatingActionButton
import android.util.AttributeSet
import android.view.View
class ScrollAwareFABBehavior(context: Context, attrs: AttributeSet) : CoordinatorLayout.Behavior<FloatingActionButton>() {
override fun onStartNestedScroll(coordinatorLayout: CoordinatorLayout, child: FloatingActionButton,
directTargetChild: View, target: View, nestedScrollAxes: Int): Boolean {
return true
}
override fun onNestedScroll(coordinatorLayout: CoordinatorLayout,
child: FloatingActionButton,
target: View, dxConsumed: Int, dyConsumed: Int,
dxUnconsumed: Int, dyUnconsumed: Int) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed)
if (dyConsumed > 0 && child.visibility == View.VISIBLE) {
child.hide(object : FloatingActionButton.OnVisibilityChangedListener() {
override fun onHidden(fab: FloatingActionButton?) {
super.onHidden(fab)
fab!!.visibility = View.INVISIBLE
}
})
} else if (dyConsumed < 0 && child.visibility != View.VISIBLE) {
child.show()
}
}
}

View File

@ -1,5 +1,8 @@
package apps.amine.bou.readerforselfoss.utils.customtabs; package apps.amine.bou.readerforselfoss.utils.customtabs;
import java.util.List;
import android.app.Activity; import android.app.Activity;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
@ -8,8 +11,6 @@ import android.support.customtabs.CustomTabsIntent;
import android.support.customtabs.CustomTabsServiceConnection; import android.support.customtabs.CustomTabsServiceConnection;
import android.support.customtabs.CustomTabsSession; import android.support.customtabs.CustomTabsSession;
import java.util.List;
/** /**
* This is a helper class to manage the connection to the Custom Tabs Service. * This is a helper class to manage the connection to the Custom Tabs Service.
*/ */

View File

@ -1,5 +1,9 @@
package apps.amine.bou.readerforselfoss.utils.customtabs; package apps.amine.bou.readerforselfoss.utils.customtabs;
import java.util.ArrayList;
import java.util.List;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
@ -9,10 +13,8 @@ import android.net.Uri;
import android.support.customtabs.CustomTabsService; import android.support.customtabs.CustomTabsService;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import apps.amine.bou.readerforselfoss.utils.customtabs.helpers.KeepAliveService;
import java.util.ArrayList; import apps.amine.bou.readerforselfoss.utils.customtabs.helpers.KeepAliveService;
import java.util.List;
@SuppressWarnings("ALL") @SuppressWarnings("ALL")
class CustomTabsHelper { class CustomTabsHelper {
@ -101,7 +103,7 @@ class CustomTabsHelper {
List<ResolveInfo> handlers = pm.queryIntentActivities( List<ResolveInfo> handlers = pm.queryIntentActivities(
intent, intent,
PackageManager.GET_RESOLVED_FILTER); PackageManager.GET_RESOLVED_FILTER);
if (handlers == null || handlers.size() == 0) { if (handlers == null || handlers.isEmpty()) {
return false; return false;
} }
for (ResolveInfo resolveInfo : handlers) { for (ResolveInfo resolveInfo : handlers) {

View File

@ -1,11 +1,12 @@
package apps.amine.bou.readerforselfoss.utils.customtabs; package apps.amine.bou.readerforselfoss.utils.customtabs;
import java.lang.ref.WeakReference;
import android.content.ComponentName; import android.content.ComponentName;
import android.support.customtabs.CustomTabsClient; import android.support.customtabs.CustomTabsClient;
import android.support.customtabs.CustomTabsServiceConnection; import android.support.customtabs.CustomTabsServiceConnection;
import java.lang.ref.WeakReference;
/** /**
* Implementation for the CustomTabsServiceConnection that avoids leaking the * Implementation for the CustomTabsServiceConnection that avoids leaking the
* ServiceConnectionCallback * ServiceConnectionCallback

View File

@ -5,14 +5,11 @@ import android.support.annotation.LayoutRes
import android.support.annotation.StringRes import android.support.annotation.StringRes
import android.view.View import android.view.View
import android.widget.TextView import android.widget.TextView
import apps.amine.bou.readerforselfoss.R
import com.mikepenz.materialdrawer.holder.BadgeStyle import com.mikepenz.materialdrawer.holder.BadgeStyle
import com.mikepenz.materialdrawer.holder.StringHolder import com.mikepenz.materialdrawer.holder.StringHolder
import com.mikepenz.materialdrawer.model.interfaces.ColorfulBadgeable import com.mikepenz.materialdrawer.model.interfaces.ColorfulBadgeable
import apps.amine.bou.readerforselfoss.R
class CustomUrlPrimaryDrawerItem : CustomUrlBasePrimaryDrawerItem<CustomUrlPrimaryDrawerItem, CustomUrlPrimaryDrawerItem.ViewHolder>(), ColorfulBadgeable<CustomUrlPrimaryDrawerItem> { class CustomUrlPrimaryDrawerItem : CustomUrlBasePrimaryDrawerItem<CustomUrlPrimaryDrawerItem, CustomUrlPrimaryDrawerItem.ViewHolder>(), ColorfulBadgeable<CustomUrlPrimaryDrawerItem> {
protected var mBadge: StringHolder = StringHolder("") protected var mBadge: StringHolder = StringHolder("")

View File

@ -0,0 +1,29 @@
package apps.amine.bou.readerforselfoss.utils.glide
import android.content.Context
import android.graphics.Bitmap
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory
import android.widget.ImageView
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.BitmapImageViewTarget
fun Context.bitmapCenterCrop(url: String, iv: ImageView) =
Glide.with(this).asBitmap().load(url).apply(RequestOptions.centerCropTransform()).into(iv)
fun Context.bitmapFitCenter(url: String, iv: ImageView) =
Glide.with(this).asBitmap().load(url).apply(RequestOptions.fitCenterTransform()).into(iv)
fun Context.circularBitmapDrawable(url: String, iv: ImageView) =
Glide.with(this)
.asBitmap()
.load(url)
.apply(RequestOptions.centerCropTransform()).
into(object : BitmapImageViewTarget(iv) {
override fun setResource(resource: Bitmap?) {
val circularBitmapDrawable = RoundedBitmapDrawableFactory.create(resources, resource)
circularBitmapDrawable.isCircular = true
iv.setImageDrawable(circularBitmapDrawable)
}
})

View File

@ -0,0 +1,32 @@
package apps.amine.bou.readerforselfoss.utils.glide
import android.content.Context
import apps.amine.bou.readerforselfoss.utils.Config
import apps.amine.bou.readerforselfoss.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 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 pref = context?.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
if (pref.getBoolean("isSelfSignedCert", false)) {
val client = getUnsafeHttpClient().build()
registry?.append(GlideUrl::class.java, InputStream::class.java,
com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader.Factory(client))
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 953 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 434 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 655 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 307 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 542 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 466 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 768 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 500 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 573 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -2,12 +2,12 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item <item
android:drawable="@color/white"/> android:drawable="@color/ic_launcher_background"/>
<item> <item>
<bitmap <bitmap
android:gravity="center" android:gravity="center"
android:src="@mipmap/ic_launcher"/> android:src="@mipmap/ic_launcher_foreground"/>
</item> </item>
</layer-list> </layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

View File

@ -0,0 +1,4 @@
<vector android:height="24dp" android:viewportHeight="24.0"
android:viewportWidth="24.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FFFFFF" android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/>
</vector>

View File

@ -0,0 +1,4 @@
<vector android:height="24dp" android:viewportHeight="24.0"
android:viewportWidth="24.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FFFFFF" android:pathData="M1,21h4L5,9L1,9v12zM23,10c0,-1.1 -0.9,-2 -2,-2h-6.31l0.95,-4.57 0.03,-0.32c0,-0.41 -0.17,-0.79 -0.44,-1.06L14.17,1 7.59,7.59C7.22,7.95 7,8.45 7,9v10c0,1.1 0.9,2 2,2h9c0.83,0 1.54,-0.5 1.84,-1.22l3.02,-7.05c0.09,-0.23 0.14,-0.47 0.14,-0.73v-1.91l-0.01,-0.01L23,10z"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -15,7 +15,8 @@
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
app:theme="@style/ToolBarStyle" /> app:theme="@style/ToolBarStyle"
app:popupTheme="?attr/toolbarPopupTheme" />
</android.support.design.widget.AppBarLayout> </android.support.design.widget.AppBarLayout>

View File

@ -9,7 +9,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<com.github.stkent.amplify.prompt.DefaultLayoutPromptView <com.github.stkent.amplify.prompt.DefaultLayoutPromptView
android:id="@+id/prompt_view" android:id="@+id/promptView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:prompt_view_user_opinion_question_title="@string/rating_prompt_title" app:prompt_view_user_opinion_question_title="@string/rating_prompt_title"
@ -34,7 +34,7 @@
android:id="@+id/coordLayout" android:id="@+id/coordLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_below="@id/prompt_view"> android:layout_below="@id/promptView">
<android.support.design.widget.CoordinatorLayout <android.support.design.widget.CoordinatorLayout
android:id="@+id/intern_coordLayout" android:id="@+id/intern_coordLayout"
@ -51,10 +51,11 @@
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar <android.support.v7.widget.Toolbar
android:id="@+id/toolbar" android:id="@+id/toolBar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
app:theme="@style/ToolBarStyle" /> app:theme="@style/ToolBarStyle"
app:popupTheme="?attr/toolbarPopupTheme" />
</android.support.design.widget.AppBarLayout> </android.support.design.widget.AppBarLayout>
@ -88,7 +89,7 @@
android:background="@color/transparent" android:background="@color/transparent"
android:visibility="gone" /> android:visibility="gone" />
<android.support.v7.widget.RecyclerView <android.support.v7.widget.RecyclerView
android:id="@+id/my_recycler_view" android:id="@+id/recyclerView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@color/transparent" android:background="@color/transparent"

View File

@ -14,7 +14,8 @@
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
app:theme="@style/ToolBarStyle" /> app:theme="@style/ToolBarStyle"
app:popupTheme="?attr/toolbarPopupTheme" />
</android.support.design.widget.AppBarLayout> </android.support.design.widget.AppBarLayout>
<LinearLayout <LinearLayout
@ -27,7 +28,7 @@
android:paddingTop="@dimen/activity_vertical_margin"> android:paddingTop="@dimen/activity_vertical_margin">
<!-- Login progress --> <!-- Login progress -->
<ProgressBar <ProgressBar
android:id="@+id/login_progress" android:id="@+id/loginProgress"
style="?android:attr/progressBarStyleLarge" style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -35,7 +36,7 @@
android:visibility="gone"/> android:visibility="gone"/>
<ScrollView <ScrollView
android:id="@+id/login_form" android:id="@+id/loginForm"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
@ -51,15 +52,13 @@
> >
<EditText <EditText
android:id="@+id/url" android:id="@+id/urlView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/prompt_url" android:hint="@string/prompt_url"
android:imeActionId="@+id/login"
android:inputType="textUri"
android:imeOptions="actionUnspecified" android:imeOptions="actionUnspecified"
android:maxLines="1" android:inputType="textUri"
/> android:maxLines="1" />
</android.support.design.widget.TextInputLayout> </android.support.design.widget.TextInputLayout>
@ -77,13 +76,12 @@
android:visibility="gone"> android:visibility="gone">
<AutoCompleteTextView <AutoCompleteTextView
android:id="@+id/login" android:id="@+id/loginView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/prompt_login" android:hint="@string/prompt_login"
android:inputType="text" android:inputType="text"
android:maxLines="1" android:maxLines="1" />
/>
</android.support.design.widget.TextInputLayout> </android.support.design.widget.TextInputLayout>
@ -94,13 +92,12 @@
android:visibility="gone"> android:visibility="gone">
<EditText <EditText
android:id="@+id/password" android:id="@+id/passwordView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/prompt_password" android:hint="@string/prompt_password"
android:inputType="textPassword" android:inputType="textPassword"
android:maxLines="1" android:maxLines="1" />
/>
</android.support.design.widget.TextInputLayout> </android.support.design.widget.TextInputLayout>
@ -118,7 +115,7 @@
android:visibility="gone"> android:visibility="gone">
<EditText <EditText
android:id="@+id/httpLogin" android:id="@+id/httpLoginView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/prompt_http_login" /> android:hint="@string/prompt_http_login" />
@ -131,15 +128,29 @@
android:visibility="gone"> android:visibility="gone">
<EditText <EditText
android:id="@+id/httpPassword" android:id="@+id/httpPasswordView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:inputType="textPassword" android:hint="@string/prompt_http_password"
android:hint="@string/prompt_http_password" /> android:inputType="textPassword" />
</android.support.design.widget.TextInputLayout> </android.support.design.widget.TextInputLayout>
<Switch
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 <Button
android:id="@+id/email_sign_in_button" android:id="@+id/signInButton"
style="?android:textAppearanceSmall" style="?android:textAppearanceSmall"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@ -1,126 +1,148 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout <android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
tools:context="apps.amine.bou.readerforselfoss.ReaderActivity"
android:background="?android:attr/windowBackground">
<ImageView <android.support.v4.widget.NestedScrollView
android:id="@+id/imageView" android:id="@+id/nestedScrollView"
android:layout_width="0dp"
android:layout_height="200dp"
android:scaleType="centerCrop"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
<TextView
android:id="@+id/source"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView" />
<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="6dp"
android:textAppearance="@style/TextAppearance.AppCompat.Headline"
android:textStyle="bold"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/source" />
<org.sufficientlysecure.htmltextview.HtmlTextView
android:id="@+id/content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="24dp"
android:paddingBottom="48dp"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title"
tools:text="Some text @android:string/fingerprint_icon_content_description" />
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="35dp" android:layout_height="match_parent">
android:layout_marginBottom="0dp"
android:layout_marginEnd="0dp" <android.support.constraint.ConstraintLayout
android:layout_marginRight="16dp" android:layout_width="match_parent"
android:layout_marginTop="16dp" android:layout_height="match_parent">
android:background="#BBBBBB"
<ImageView
android:id="@+id/imageView"
android:layout_width="0dp"
android:layout_height="200dp"
android:scaleType="centerCrop"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
<TextView
android:id="@+id/source"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView" />
<TextView
android:id="@+id/titleView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="6dp"
android:textAppearance="@style/TextAppearance.AppCompat.Headline"
android:textStyle="bold"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/source" />
<TextView
android:id="@+id/content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="24dp"
android:paddingBottom="48dp"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/titleView"
android:textColorLink="?attr/colorAccent"/>
<!--<org.sufficientlysecure.htmltextview.HtmlTextView
android:id="@+id/content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="24dp"
android:paddingBottom="48dp"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/titleView" />-->
</android.support.constraint.ConstraintLayout>
</android.support.v4.widget.NestedScrollView>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" android:layout_gravity="end|bottom|right">
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/content"
app:layout_constraintVertical_bias="1.0">
<ImageButton <com.github.rubensousa.floatingtoolbar.FloatingToolbar
android:id="@+id/browserBtn" android:id="@+id/floatingToolbar"
android:layout_width="35dp" android:layout_width="match_parent"
android:layout_height="35dp" android:layout_height="?attr/actionBarSize"
android:layout_centerVertical="true" android:layout_gravity="bottom"
app:floatingItemBackground="?attr/colorAccent"
app:floatingMenu="@menu/reader_toolbar" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_gravity="end|bottom|right"
android:layout_marginBottom="16dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:layout_marginRight="16dp" android:layout_marginRight="16dp"
android:layout_toLeftOf="@+id/shareBtn" android:paddingBottom="@dimen/activity_vertical_margin"
android:layout_toStartOf="@+id/shareBtn" android:paddingTop="@dimen/activity_vertical_margin"
android:adjustViewBounds="true" android:src="@drawable/ic_add"
android:background="@android:color/transparent" app:backgroundTint="?attr/colorAccent"
android:elevation="5dp" app:fabSize="mini"
android:padding="4dp" app:rippleColor="?attr/colorAccentDark" />
android:scaleType="centerCrop" </FrameLayout>
android:src="@drawable/ic_open_in_browser_black_24dp"
android:tint="?android:attr/textColorPrimary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/shareBtn"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageButton <FrameLayout
android:id="@+id/shareBtn" android:id="@+id/progressBar"
android:layout_width="35dp" android:layout_width="match_parent"
android:layout_height="35dp" android:layout_height="match_parent"
android:adjustViewBounds="true" android:visibility="gone"
android:background="@android:color/transparent" android:animateLayoutChanges="true"
android:elevation="5dp" android:alpha="0.8"
android:padding="4dp" android:background="@color/black"
android:scaleType="centerCrop" android:clickable="false">
android:src="@drawable/ic_share_black_24dp"
android:tint="?android:attr/textColorPrimary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/browserBtn"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout> <ProgressBar
style="?android:attr/progressBarStyleLarge"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:progressTint="?attr/colorAccent" />
</FrameLayout>
</android.support.design.widget.CoordinatorLayout>
</android.support.constraint.ConstraintLayout>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout <android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
@ -16,28 +16,26 @@
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
app:theme="@style/ToolBarStyle" /> app:theme="@style/ToolBarStyle"
app:popupTheme="?attr/toolbarPopupTheme" />
</android.support.design.widget.AppBarLayout> </android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView <android.support.v7.widget.RecyclerView
android:id="@+id/activity_sources" android:id="@+id/recyclerView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:scrollbars="vertical" android:scrollbars="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior"> app:layout_behavior="@string/appbar_scrolling_view_behavior">
</android.support.v7.widget.RecyclerView> </android.support.v7.widget.RecyclerView>
<com.melnykov.fab.FloatingActionButton <android.support.design.widget.FloatingActionButton
android:id="@+id/fab" android:id="@+id/fab"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="end|bottom|right" android:layout_gravity="end|bottom|right"
android:src="@drawable/ic_add_black_24dp" android:src="@drawable/ic_add"
android:tint="?android:textColorPrimary" android:tint="?android:textColorPrimary"
fab:fab_colorNormal="?attr/colorAccent"
fab:fab_colorPressed="?attr/colorAccentDark"
fab:fab_colorRipple="@color/pink"
android:paddingBottom="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingTop="@dimen/activity_vertical_margin" android:paddingTop="@dimen/activity_vertical_margin"
android:layout_alignParentBottom="true" android:layout_alignParentBottom="true"
@ -45,5 +43,6 @@
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:layout_marginBottom="16dp" android:layout_marginBottom="16dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"/> android:layout_marginRight="16dp"
</RelativeLayout> app:layout_behavior="apps.amine.bou.readerforselfoss.utils.ScrollAwareFABBehavior" />
</android.support.design.widget.CoordinatorLayout>

View File

@ -1,157 +1,149 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <android.support.v7.widget.CardView
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_height="wrap_content"> xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="8dp"
app:layout_constraintHorizontal_bias="0.62"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
card_view:cardElevation="2dp"
card_view:cardUseCompatPadding="true"
card_view:layout_constraintBottom_toBottomOf="parent">
<android.support.v7.widget.CardView <android.support.constraint.ConstraintLayout
android:id="@+id/card" android:layout_width="match_parent"
xmlns:card_view="http://schemas.android.com/apk/res-auto" android:layout_height="wrap_content">
android:layout_width="0dp"
app:layout_constraintRight_toRightOf="parent" <ImageView
app:layout_constraintLeft_toLeftOf="parent" android:id="@+id/itemImage"
app:layout_constraintHorizontal_bias="0.62" android:layout_width="0dp"
app:layout_constraintTop_toTopOf="parent" android:layout_height="wrap_content"
card_view:cardElevation="2dp" android:adjustViewBounds="true"
card_view:cardUseCompatPadding="true" android:cropToPadding="true"
android:layout_height="wrap_content" app:layout_constraintLeft_toLeftOf="parent"
android:layout_marginStart="8dp" app:layout_constraintRight_toRightOf="parent"
android:layout_marginLeft="8dp" app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="8dp" app:srcCompat="@drawable/background_splash"
android:layout_marginEnd="8dp" card_view:layout_constraintBottom_toTopOf="@+id/constraintLayout" />
android:layout_marginRight="8dp">
<android.support.constraint.ConstraintLayout <android.support.constraint.ConstraintLayout
android:layout_width="match_parent" android:id="@+id/constraintLayout"
android:layout_height="wrap_content"> android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/itemImage">
<ImageView <ImageView
android:layout_width="0dp" android:id="@+id/sourceImage"
android:layout_height="wrap_content" android:layout_width="40dp"
app:srcCompat="@drawable/background_splash" android:layout_height="40dp"
android:id="@+id/itemImage" android:layout_marginLeft="8dp"
android:scaleType="fitCenter" android:layout_marginStart="8dp"
android:adjustViewBounds="true" android:layout_marginTop="8dp"
android:cropToPadding="true"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="0dp"
android:layout_marginRight="0dp"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginLeft="0dp"
app:layout_constraintLeft_toLeftOf="parent" />
<android.support.constraint.ConstraintLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/itemImage"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/background_splash" />
<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginStart="8dp"
android:gravity="start"
android:textAlignment="viewStart"
android:textStyle="bold"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toRightOf="@+id/sourceImage"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@+id/sourceImage"
tools:text="Titre" />
<TextView
android:id="@+id/sourceTitleAndDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="start"
android:textAlignment="viewStart"
android:textSize="14sp"
app:layout_constraintLeft_toLeftOf="@+id/title"
app:layout_constraintTop_toBottomOf="@+id/title"
tools:text="Google Actualité Il y a 5h" />
<RelativeLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
> app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/sourceTitleAndDate">
<ImageView <com.like.LikeButton
android:layout_height="40dp" android:id="@+id/favButton"
app:srcCompat="@drawable/background_splash" android:layout_width="35dp"
android:id="@+id/sourceImage" android:layout_height="35dp"
app:layout_constraintLeft_toLeftOf="parent" android:layout_alignParentEnd="true"
android:layout_marginStart="8dp" android:layout_alignParentRight="true"
android:layout_marginLeft="8dp"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="8dp"
android:layout_width="40dp" />
<TextView android:layout_centerVertical="true"
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp" android:layout_marginRight="8dp"
android:layout_marginStart="8dp" android:elevation="5dp"
android:gravity="start" android:padding="4dp"
android:textAlignment="viewStart" app:icon_size="22dp"
android:textStyle="bold" app:icon_type="heart" />
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toRightOf="@+id/sourceImage"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@+id/sourceImage"
tools:text="Titre" />
<TextView <ImageButton
android:layout_width="wrap_content" android:id="@+id/shareBtn"
android:layout_height="wrap_content" android:layout_width="35dp"
android:id="@+id/sourceTitleAndDate" android:layout_height="35dp"
android:textSize="14sp" android:layout_centerVertical="true"
tools:text="Google Actualité Il y a 5h" android:layout_marginEnd="16dp"
android:textAlignment="viewStart" android:layout_marginRight="16dp"
app:layout_constraintTop_toBottomOf="@+id/title" android:layout_toLeftOf="@+id/favButton"
android:layout_marginTop="8dp" android:layout_toStartOf="@+id/favButton"
app:layout_constraintLeft_toLeftOf="@+id/title" android:adjustViewBounds="true"
android:gravity="start" /> android:background="@android:color/transparent"
android:elevation="5dp"
android:padding="4dp"
android:scaleType="centerCrop"
android:src="@drawable/ic_share_black_24dp"
android:tint="?android:attr/textColorPrimary" />
<RelativeLayout <ImageButton
android:layout_width="0dp" android:id="@+id/browserBtn"
android:layout_height="wrap_content" android:layout_width="35dp"
app:layout_constraintTop_toBottomOf="@+id/sourceTitleAndDate" android:layout_height="35dp"
android:layout_marginTop="16dp" android:layout_centerVertical="true"
app:layout_constraintLeft_toLeftOf="parent" android:layout_marginEnd="16dp"
app:layout_constraintRight_toRightOf="parent" android:layout_marginRight="16dp"
app:layout_constraintBottom_toBottomOf="parent"> android:layout_toLeftOf="@+id/shareBtn"
android:layout_toStartOf="@+id/shareBtn"
android:adjustViewBounds="true"
android:background="@android:color/transparent"
android:elevation="5dp"
android:padding="4dp"
android:scaleType="centerCrop"
android:src="@drawable/ic_open_in_browser_black_24dp"
android:tint="?android:attr/textColorPrimary" />
<com.like.LikeButton </RelativeLayout>
app:icon_type="heart"
app:icon_size="22dp"
android:id="@+id/favButton"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_centerVertical="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:elevation="5dp"
android:padding="4dp"
android:layout_marginRight="8dp"
android:layout_marginEnd="8dp"/>
<ImageButton
android:id="@+id/shareBtn"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_centerVertical="true"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_toLeftOf="@+id/favButton"
android:layout_toStartOf="@+id/favButton"
android:adjustViewBounds="true"
android:background="@android:color/transparent"
android:elevation="5dp"
android:padding="4dp"
android:scaleType="centerCrop"
android:src="@drawable/ic_share_black_24dp"
android:tint="?android:attr/textColorPrimary" />
<ImageButton
android:id="@+id/browserBtn"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_centerVertical="true"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_toLeftOf="@+id/shareBtn"
android:layout_toStartOf="@+id/shareBtn"
android:adjustViewBounds="true"
android:background="@android:color/transparent"
android:elevation="5dp"
android:padding="4dp"
android:scaleType="centerCrop"
android:src="@drawable/ic_open_in_browser_black_24dp"
android:tint="?android:attr/textColorPrimary" />
</RelativeLayout>
</android.support.constraint.ConstraintLayout>
</android.support.constraint.ConstraintLayout> </android.support.constraint.ConstraintLayout>
</android.support.constraint.ConstraintLayout>
</android.support.v7.widget.CardView> </android.support.v7.widget.CardView>
</android.support.constraint.ConstraintLayout>

View File

@ -9,6 +9,7 @@
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
app:theme="@style/ToolBarStyle" /> app:theme="@style/ToolBarStyle"
app:popupTheme="?attr/toolbarPopupTheme" />
</android.support.design.widget.AppBarLayout> </android.support.design.widget.AppBarLayout>

View File

@ -5,14 +5,14 @@
<item android:id="@+id/action_search" <item android:id="@+id/action_search"
android:title="@string/menu_home_search" android:title="@string/menu_home_search"
android:icon="@drawable/ic_action_search" android:icon="@drawable/ic_action_search"
app:showAsAction="always|collapseActionView" app:showAsAction="ifRoom|collapseActionView"
app:actionViewClass="android.support.v7.widget.SearchView" /> app:actionViewClass="android.support.v7.widget.SearchView" />
<item android:id="@+id/readAll" <item android:id="@+id/readAll"
android:icon="@drawable/ic_done_all_white_24dp" android:icon="@drawable/ic_done_all_white_24dp"
android:title="@string/readAll" android:title="@string/readAll"
android:orderInCategory="1" android:orderInCategory="1"
app:showAsAction="always"/> app:showAsAction="ifRoom"/>
<item <item
android:id="@+id/refresh" android:id="@+id/refresh"

View File

@ -2,6 +2,14 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/login_debug"
android:checkable="true"
android:checked="false"
android:icon="@drawable/ic_bug_report"
android:title="@string/login_menu_debug"
app:showAsAction="never" />
<item android:id="@+id/about" <item android:id="@+id/about"
android:title="@string/action_about" android:title="@string/action_about"
android:orderInCategory="102" android:orderInCategory="102"

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/more_action"
android:icon="@drawable/ic_chrome_reader_mode"
android:title="@string/reader_action_more"
app:showAsAction="ifRoom" />
<item
android:id="@+id/open_action"
android:icon="@drawable/ic_open_in_browser"
android:iconTint="@color/white"
android:title="@string/reader_action_open"
app:showAsAction="ifRoom" />
<item
android:id="@+id/share_action"
android:icon="@drawable/ic_share_white_24dp"
android:title="@string/reader_action_share"
app:showAsAction="ifRoom" />
</menu>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 B

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