Compare commits

..

140 Commits

Author SHA1 Message Date
113dfa68be Changelog. 2017-11-30 19:40:03 +01:00
60c6514fa1 New translations strings.xml (Turkish) 2017-11-30 19:38:40 +01:00
114485afc3 Missing changelog. 2017-11-29 20:28:27 +01:00
d6a51381b9 New Crowdin translations (#133)
* New translations strings.xml (Afrikaans)

* New translations strings.xml (Korean)

* New translations strings.xml (English, Indonesia)

* New translations strings.xml (Vietnamese)

* 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 (Portuguese)

* New translations strings.xml (Polish)

* New translations strings.xml (Norwegian)

* New translations strings.xml (Japanese)

* New translations strings.xml (Arabic)

* New translations strings.xml (Italian)

* 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 (Chinese Simplified)

* New translations strings.xml (Catalan)

* New translations strings.xml (Indonesian)
2017-11-29 19:10:47 +01:00
620f13fd7c Closes #132. 2017-11-29 19:09:16 +01:00
6577b2c3d7 Update Crowdin configuration file 2017-11-29 13:27:00 +01:00
caef522c8b Changelog. 2017-11-28 20:16:09 +01:00
40ea07de2e Last fix for the endless scroll. 2017-11-28 20:10:32 +01:00
7905e4aa12 Fixes #127. 2017-11-28 19:18:43 +01:00
64f4fd708a Merge pull request #126 from aminecmi/translate_master
New Crowdin translations
2017-11-28 19:17:01 +01:00
b46e4a018f New translations strings.xml (Chinese Simplified) 2017-11-28 06:21:12 +01:00
3e999a9be2 New translations strings.xml (Chinese Simplified) 2017-11-28 06:11:20 +01:00
f656d621e6 New translations strings.xml (Chinese Simplified) 2017-11-28 06:01:37 +01:00
951cc1e6bd New translations strings.xml (Chinese Simplified) 2017-11-27 10:42:33 +01:00
d8d4264f1b New translations strings.xml (Chinese Simplified) 2017-11-27 10:33:30 +01:00
014eeec2b9 New translations strings.xml (Chinese Simplified) 2017-11-27 08:10:29 +01:00
83837bddc3 New translations strings.xml (Chinese Simplified) 2017-11-27 07:51:23 +01:00
f97666db92 New translations strings.xml (Chinese Simplified) 2017-11-27 07:00:51 +01:00
ee08ea41a1 New translations strings.xml (Chinese Simplified) 2017-11-27 06:50:49 +01:00
4ca64610cb New translations strings.xml (Chinese Simplified) 2017-11-27 06:40:37 +01:00
4980145e46 New translations strings.xml (Chinese Simplified) 2017-11-27 05:50:32 +01:00
10cbc19a0c New translations strings.xml (Chinese Simplified) 2017-11-27 05:40:32 +01:00
15fba2b29b New translations strings.xml (Chinese Simplified) 2017-11-27 05:20:28 +01:00
096952f88c New translations strings.xml (Chinese Simplified) 2017-11-27 05:10:31 +01:00
0ea70c1922 New translations strings.xml (Chinese Simplified) 2017-11-27 05:00:30 +01:00
69ac2e2b44 Kotlin style guide. (#125) 2017-11-25 13:12:12 +01:00
68098f4d84 Fixes #124. 2017-11-24 19:49:03 +01:00
080d52893e Changelog. 2017-11-23 21:57:10 +01:00
b02334a8d4 New Crowdin translations (#123)
* 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 (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 (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)

* New translations strings.xml (Portuguese, Brazilian)
2017-11-23 21:55:57 +01:00
27118add22 Closes #118. 2017-11-23 21:29:45 +01:00
2a6f98a1e8 Should fix #119. 2017-11-23 21:27:01 +01:00
1f67f2fdee Fixes #121. 2017-11-21 19:05:14 +01:00
ebf4d294a8 Fixing infinite scroll trying to load more items when there are no more. 2017-11-20 21:08:13 +01:00
4a4dbacc95 Changelog. 2017-11-18 15:19:57 +01:00
687839b5f8 Infinite scroll should be working as expected. Fixes #116. 2017-11-18 15:09:44 +01:00
8fb339034f Fixed #117. 2017-11-16 19:37:19 +01:00
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
165 changed files with 8328 additions and 2145 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.
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.
@ -22,7 +22,8 @@ Always check if the web version of your instance is working.
### Bug reports/Feature request
* 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)
### 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.
* 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.
* 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.
* Always check your changes and discard the ones that are irrelevant to your feature or bugfix.
* 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).
* Remember that PR review can take time.
# Build the project
You can directly import this project into IntellIJ/Android Studio.
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#).
- Define the following in `res/values/strings.xml` or create `res/values/secrets.xml`
- mercuryApiKey: A [Mercury](https://mercury.postlight.com/web-parser/) web parser api key for the internal browser
- 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
- feedback_email: An email to receive users feedback.
- source_url: an url to the source code, used in the settings
- tracker_url: an url to the tracker, used in the settings
### Examples:
#### Inside ~/.gradle/gradle.properties
```
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
secrets.xml
release/
mipmap-*
release/
crowdin.properties

View File

@ -1,4 +1,223 @@
**1.5.1.9**
**1.5.4.20**
- Turkish translation !
**1.5.4.19**
- Fixed an issue with crowdin configuration (and its translations)
**1.5.4.18**
- Typo fix.
- The real last infinite scroll bug fix.
- Simplified Chinese translation !
**1.5.4.17**
- Fixed the last bug with infinite scroll.
**1.5.4.16**
- Fixing list view displaying issues.
- Endless scroll is not in beta anymore.
**1.5.4.15**
- Fixed an issue with the sources list.
**1.5.4.14**
- Fixing infinite scroll trying to load more items when there are no more.
**1.5.4.13**
- Displaying the right number of items.
- Fixing infinite scroll remaining issues. Should be stable enough.
**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.
@ -210,4 +429,4 @@ _Updates_
**1.3.3.4**
...
...

View File

@ -1,17 +1,17 @@
# 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)
[![Build Status](http://jenkins.amine-bou.fr/job/ReaderForSelfoss/badge/icon)](http://jenkins.amine-bou.fr/job/ReaderForSelfoss/)
[![Gitter chat](https://badges.gitter.im/amine-bou/ReaderForSelfoss.png)](https://gitter.im/amine-bou/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)
This is the repo of [Reader For Selfoss](https://play.google.com/store/apps/details?id=apps.amine.bou.readerforselfoss&hl=en).
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 ?
@ -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)
- [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)
- [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: 'io.fabric'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
repositories {
maven { url 'https://maven.fabric.io/public' }
maven {
url 'https://maven.fabric.io/public'
}
}
android {
compileSdkVersion 26
buildToolsVersion "26.0.0"
compileSdkVersion 27
buildToolsVersion '27.0.0'
defaultConfig {
applicationId "apps.amine.bou.readerforselfoss"
minSdkVersion 16
targetSdkVersion 26
versionCode 1519
versionName "1.5.1.9"
targetSdkVersion 27
versionCode versionCodeFromGit()
versionName versionNameFromGit()
// Enabling multidex support.
multiDexEnabled true
@ -38,10 +59,18 @@ android {
// tests
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 {
release {
minifyEnabled false
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
}
@ -68,77 +97,79 @@ android {
dependencies {
// Testing
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
androidTestCompile 'com.android.support.test:runner:0.5'
androidTestCompile 'com.android.support.test.espresso:espresso-core:3.0.1'
androidTestCompile 'com.android.support.test:runner:1.0.1'
// 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
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 "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
// Android Support
compile 'com.android.support:appcompat-v7:26.0.0'
compile 'com.android.support:design:26.0.0'
compile 'com.android.support:recyclerview-v7:26.0.0'
compile 'com.android.support:support-v4:26.0.0'
compile 'com.android.support:support-vector-drawable:26.0.0'
compile 'com.android.support:customtabs:26.0.0'
compile 'com.android.support:cardview-v7:26.0.0'
compile 'com.android.support:appcompat-v7:27.0.0'
compile 'com.android.support:design:27.0.0'
compile 'com.android.support:recyclerview-v7:27.0.0'
compile 'com.android.support:support-v4:27.0.0'
compile 'com.android.support:support-vector-drawable:27.0.0'
compile 'com.android.support:customtabs:27.0.0'
compile 'com.android.support:cardview-v7:27.0.0'
compile 'com.android.support.constraint:constraint-layout:1.0.2'
// Firebase + crashlytics
compile 'com.google.firebase:firebase-core:11.0.2'
compile 'com.google.firebase:firebase-config:11.0.2'
compile 'com.google.firebase:firebase-invites:11.0.2'
compile('com.crashlytics.sdk.android:crashlytics:2.6.8@aar') {
transitive = true
compile 'com.google.firebase:firebase-core:11.4.2'
compile 'com.google.firebase:firebase-config:11.4.2'
compile 'com.google.firebase:firebase-invites:11.4.2'
compile('com.crashlytics.sdk.android:crashlytics:2.7.1@aar') {
transitive = true;
}
//multidex
compile 'com.android.support:multidex:1.0.1'
compile 'com.android.support:multidex:1.0.2'
// Intro
compile 'agency.tango.android:material-intro-screen:0.0.5'
// About
compile('com.mikepenz:aboutlibraries:5.9.6@aar') {
compile('com.mikepenz:aboutlibraries:6.0.0@aar') {
transitive = true
}
// Retrofit + http logging + okhttp
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.burgstaller:okhttp-digest:1.12'
// Material-ish things
compile 'com.ashokvarma.android:bottom-navigation-bar:2.0.2'
compile 'com.melnykov:floatingactionbutton:1.3.0'
compile 'com.ashokvarma.android:bottom-navigation-bar:2.0.3'
compile 'com.github.jd-alexander:LikeButton:0.2.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
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
compile 'com.github.stkent:amplify:1.5.0'
compile 'com.github.stkent:amplify:2.1.0'
// For the article reader
compile 'com.klinkerapps:drag-dismiss-activity:1.4.2'
compile 'com.klinkerapps:drag-dismiss-activity:1.5.0'
// Drawer
compile('com.mikepenz:materialdrawer:5.9.4@aar') {
transitive = true
}
implementation 'co.zsmb:materialdrawer-kt:1.2.1'
compile 'com.anupcowkur:reservoir:3.1.0'
// Themes
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'
@ -147,6 +178,7 @@ apply plugin: 'com.google.gms.google-services'
afterEvaluate {
initFabricPropertiesIfNeeded()
initAppLoginPropertiesIfNeeded()
initAppForSecretPropertiesIfNeeded()
}
def initFabricPropertiesIfNeeded() {
@ -170,4 +202,19 @@ def initAppLoginPropertiesIfNeeded() {
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
-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

@ -1,4 +1,3 @@
package apps.amine.bou.readerforselfoss
// TODO: test source adding

View File

@ -5,29 +5,33 @@ import android.content.Intent
import android.support.test.InstrumentationRegistry
import android.support.test.espresso.Espresso.onView
import android.support.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
import android.support.test.espresso.action.ViewActions.*
import android.support.test.espresso.action.ViewActions.click
import android.support.test.espresso.action.ViewActions.closeSoftKeyboard
import android.support.test.espresso.action.ViewActions.pressBack
import android.support.test.espresso.action.ViewActions.pressKey
import android.support.test.espresso.action.ViewActions.typeText
import android.support.test.espresso.assertion.ViewAssertions.matches
import android.support.test.espresso.contrib.DrawerActions
import android.support.test.espresso.intent.Intents
import android.support.test.espresso.intent.Intents.intended
import android.support.test.espresso.intent.Intents.times
import android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent
import android.support.test.espresso.matcher.ViewMatchers.*
import android.support.test.espresso.matcher.ViewMatchers.isDisplayed
import android.support.test.espresso.matcher.ViewMatchers.isRoot
import android.support.test.espresso.matcher.ViewMatchers.withContentDescription
import android.support.test.espresso.matcher.ViewMatchers.withId
import android.support.test.espresso.matcher.ViewMatchers.withText
import android.support.test.rule.ActivityTestRule
import android.support.test.runner.AndroidJUnit4
import android.view.KeyEvent
import com.mikepenz.aboutlibraries.ui.LibsActivity
import apps.amine.bou.readerforselfoss.utils.Config
import com.heinrichreimersoftware.androidissuereporter.IssueReporterLauncher
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import apps.amine.bou.readerforselfoss.settings.SettingsActivity
import apps.amine.bou.readerforselfoss.utils.Config
import org.junit.After
@RunWith(AndroidJUnit4::class)
class HomeActivityEspressoTest {
lateinit var context: Context
@ -40,9 +44,9 @@ class HomeActivityEspressoTest {
context = InstrumentationRegistry.getInstrumentation().targetContext
val editor =
context
.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
.edit()
context
.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
.edit()
editor.clear()
editor.putString("url", BuildConfig.LOGIN_URL)
@ -60,35 +64,32 @@ class HomeActivityEspressoTest {
rule.launchActivity(Intent())
onView(
withMenu(
id = R.id.action_search,
titleId = R.string.menu_home_search
)
withMenu(
id = R.id.action_search,
titleId = R.string.menu_home_search
)
).perform(click())
onView(withId(R.id.search_bar)).check(matches(isDisplayed()))
onView(withId(R.id.search_src_text)).perform(typeText("android"), pressKey(KeyEvent.KEYCODE_SEARCH), closeSoftKeyboard())
onView(withId(R.id.search_src_text)).perform(
typeText("android"),
pressKey(KeyEvent.KEYCODE_SEARCH),
closeSoftKeyboard()
)
onView(withContentDescription(R.string.abc_toolbar_collapse_description)).perform(click())
onView(withMenu(id = R.id.readAll, titleId = R.string.readAll)).perform(click())
openActionBarOverflowOrOptionsMenu(context)
onView(withMenu(id = R.id.refresh, titleId = R.string.menu_home_refresh))
.perform(click())
.perform(click())
openActionBarOverflowOrOptionsMenu(context)
onView(withText(R.string.action_disconnect)).perform(click())
intended(hasComponent(LoginActivity::class.java.name), times(1))
onView(isRoot()).perform(pressBack())
}
@Test
@ -98,8 +99,9 @@ class HomeActivityEspressoTest {
onView(withId(R.id.material_drawer_layout)).perform(DrawerActions.open())
onView(withText(R.string.action_about)).perform(click())
intended(hasComponent(LibsActivity::class.java.name))
onView(withText(R.string.drawer_report_bug)).perform(click())
intended(hasComponent(IssueReporterLauncher.Activity::class.java.name))
onView(isRoot()).perform(pressBack())
onView(isRoot()).perform(pressBack())
intended(hasComponent(HomeActivity::class.java.name))
@ -107,11 +109,6 @@ class HomeActivityEspressoTest {
onView(withId(R.id.material_drawer_layout)).perform(DrawerActions.open())
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

View File

@ -1,7 +1,5 @@
package apps.amine.bou.readerforselfoss
import java.util.*
import android.content.Context
import android.content.Intent
import android.support.test.InstrumentationRegistry.getInstrumentation
@ -11,22 +9,19 @@ import android.support.test.espresso.assertion.ViewAssertions.matches
import android.support.test.espresso.intent.Intents
import android.support.test.espresso.intent.Intents.intended
import android.support.test.espresso.intent.Intents.times
import android.support.test.espresso.intent.matcher.IntentMatchers.*
import android.support.test.espresso.intent.matcher.UriMatchers.hasHost
import android.support.test.espresso.matcher.ViewMatchers.*
import android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent
import android.support.test.espresso.matcher.ViewMatchers.isDisplayed
import android.support.test.espresso.matcher.ViewMatchers.withId
import android.support.test.espresso.matcher.ViewMatchers.withText
import android.support.test.rule.ActivityTestRule
import android.support.test.runner.AndroidJUnit4
import org.hamcrest.Matchers.allOf
import org.hamcrest.Matchers.equalTo
import apps.amine.bou.readerforselfoss.utils.Config
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import apps.amine.bou.readerforselfoss.utils.Config
import org.junit.After
import java.util.*
@RunWith(AndroidJUnit4::class)
class IntroActivityEspressoTest {
@ -37,9 +32,9 @@ class IntroActivityEspressoTest {
@Before
fun clearData() {
val editor =
getInstrumentation().targetContext
.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
.edit()
getInstrumentation().targetContext
.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
.edit()
editor.clear()
editor.commit()
@ -60,7 +55,6 @@ class IntroActivityEspressoTest {
intended(hasComponent(IntroActivity::class.java.name), times(1))
intended(hasComponent(LoginActivity::class.java.name), times(1))
}
@Test
@ -75,7 +69,7 @@ class IntroActivityEspressoTest {
onView(withText(R.string.intro_hello_title)).check(matches(isDisplayed()))
onView(withId(R.id.button_next)).perform(click())
repeat(random) {_ ->
repeat(random) { _ ->
onView(withText(R.string.intro_needs_selfoss_message)).check(matches(isDisplayed()))
onView(withId(R.id.button_next)).perform(click())
onView(withText(R.string.intro_all_set_message)).check(matches(isDisplayed()))
@ -88,30 +82,6 @@ class IntroActivityEspressoTest {
intended(hasComponent(IntroActivity::class.java.name), times(1))
intended(hasComponent(LoginActivity::class.java.name), times(1))
}
@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

View File

@ -5,45 +5,48 @@ import android.content.Intent
import android.support.test.InstrumentationRegistry
import android.support.test.espresso.Espresso.onView
import android.support.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
import android.support.test.espresso.action.ViewActions.*
import android.support.test.espresso.action.ViewActions.click
import android.support.test.espresso.action.ViewActions.closeSoftKeyboard
import android.support.test.espresso.action.ViewActions.pressBack
import android.support.test.espresso.action.ViewActions.typeText
import android.support.test.espresso.assertion.ViewAssertions.matches
import android.support.test.espresso.intent.Intents
import android.support.test.espresso.intent.Intents.intended
import android.support.test.espresso.intent.Intents.times
import android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent
import android.support.test.espresso.matcher.ViewMatchers
import android.support.test.espresso.matcher.ViewMatchers.*
import android.support.test.espresso.matcher.ViewMatchers.isRoot
import android.support.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import android.support.test.espresso.matcher.ViewMatchers.withId
import android.support.test.espresso.matcher.ViewMatchers.withText
import android.support.test.rule.ActivityTestRule
import android.support.test.runner.AndroidJUnit4
import apps.amine.bou.readerforselfoss.utils.Config
import com.mikepenz.aboutlibraries.ui.LibsActivity
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import apps.amine.bou.readerforselfoss.utils.Config
import org.junit.After
@RunWith(AndroidJUnit4::class)
class LoginActivityEspressoTest {
@Rule @JvmField
val rule = ActivityTestRule(LoginActivity::class.java, true, false)
lateinit var context: Context
lateinit var url: String
lateinit var username: String
lateinit var password: String
private lateinit var context: Context
private lateinit var url: String
private lateinit var username: String
private lateinit var password: String
@Before
fun setUp() {
context = InstrumentationRegistry.getInstrumentation().targetContext
val editor =
context
.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
.edit()
context
.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
.edit()
editor.clear()
editor.commit()
@ -69,20 +72,18 @@ class LoginActivityEspressoTest {
onView(isRoot()).perform(pressBack())
intended(hasComponent(LoginActivity::class.java.name))
}
@Test
fun wrongLoginUrl() {
rule.launchActivity(Intent())
onView(withId(R.id.login_progress))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)))
onView(withId(R.id.loginProgress))
.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()))
}
@ -94,26 +95,29 @@ class LoginActivityEspressoTest {
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.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.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.email_sign_in_button)).perform(click())
onView(withId(R.id.signInButton)).perform(click())
onView(withId(R.id.passwordLayout)).check(
matches(
isHintOrErrorEnabled())
matches(
isHintOrErrorEnabled()
)
)
}
@Test
@ -121,20 +125,25 @@ class LoginActivityEspressoTest {
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.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.loginLayout)).check(matches(isHintOrErrorEnabled()))
onView(withId(R.id.passwordLayout)).check(matches(isHintOrErrorEnabled()))
}
@Test
@ -142,23 +151,27 @@ class LoginActivityEspressoTest {
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.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))
}
@After
fun releaseIntents() {
Intents.release()
}
}

View File

@ -17,7 +17,6 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class MainActivityEspressoTest {
@ -48,7 +47,6 @@ class MainActivityEspressoTest {
intended(hasComponent(MainActivity::class.java.name))
intended(hasComponent(IntroActivity::class.java.name))
intended(hasComponent(LoginActivity::class.java.name), times(0))
}
@Test
@ -61,13 +59,10 @@ class MainActivityEspressoTest {
intended(hasComponent(MainActivity::class.java.name))
intended(hasComponent(LoginActivity::class.java.name))
intended(hasComponent(IntroActivity::class.java.name), times(0))
}
@After
fun releaseIntents() {
Intents.release()
}
}

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -2,23 +2,27 @@ package apps.amine.bou.readerforselfoss
import android.content.Intent
import android.os.Bundle
import android.preference.PreferenceManager
import android.support.constraint.ConstraintLayout
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
import android.view.View
import android.widget.*
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.EditText
import android.widget.ProgressBar
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.Spout
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
import apps.amine.bou.readerforselfoss.utils.Config
import apps.amine.bou.readerforselfoss.utils.isBaseUrlValid
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() {
@ -28,39 +32,48 @@ class AddSourceActivity : AppCompatActivity() {
super.onCreate(savedInstanceState)
Scoop.getInstance().apply(this)
setContentView(R.layout.activity_add_source)
val toolbar: Toolbar = findViewById(R.id.toolbar)
setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(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
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) {
mustLoginToAddSource()
}
val intent = intent
if (Intent.ACTION_SEND == intent.action && "text/plain" == intent.type) {
mSourceUri.setText(intent.getStringExtra(Intent.EXTRA_TEXT))
mNameInput.setText(intent.getStringExtra(Intent.EXTRA_TITLE))
maybeGetDetailsFromIntentSharing(intent, sourceUri, nameInput)
saveBtn.setOnClickListener {
handleSaveSource(tags, nameInput.text.toString(), sourceUri.text.toString(), api!!)
}
mSaveBtn.setOnClickListener {
handleSaveSource(mTags, mNameInput.text.toString(), mSourceUri.text.toString(), api!!)
val config = Config(this)
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>()
mSpoutsSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
spoutsSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(adapterView: AdapterView<*>, view: View, i: Int, l: Long) {
val spoutName = (view as TextView).text.toString()
mSpoutsValue = spoutsKV[spoutName]
@ -71,48 +84,59 @@ 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.isBaseUrlValid()) {
mustLoginToAddSource()
} else {
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()
val itemsStrings = items.map { it.value.name }
for ((key, value) in items) {
spoutsKV.put(value.name, key)
}
}
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()
}
}
private fun handleProblemWithSpouts() {
Toast.makeText(this@AddSourceActivity, R.string.cant_get_spouts, Toast.LENGTH_SHORT).show()
mProgress.visibility = View.GONE
}
})
override fun onFailure(call: Call<Map<String, Spout>>, t: Throwable) {
handleProblemWithSpouts()
}
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,28 +147,41 @@ class AddSourceActivity : AppCompatActivity() {
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()
} else {
api.createSource(
title,
url,
mSpoutsValue!!,
mTags.text.toString(),
""
title,
url,
mSpoutsValue!!,
tags.text.toString(),
""
).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) {
finish()
} else {
Toast.makeText(this@AddSourceActivity, R.string.cant_create_source, Toast.LENGTH_SHORT).show()
Toast.makeText(
this@AddSourceActivity,
R.string.cant_create_source,
Toast.LENGTH_SHORT
).show()
}
}
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
Toast.makeText(this@AddSourceActivity, R.string.cant_create_source, Toast.LENGTH_SHORT).show()
Toast.makeText(
this@AddSourceActivity,
R.string.cant_create_source,
Toast.LENGTH_SHORT
).show()
}
})
}

View File

@ -1,49 +1,60 @@
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.MessageButtonBehaviour
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() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
addSlide(SlideFragmentBuilder()
.backgroundColor(R.color.colorPrimary)
.buttonsColor(R.color.colorAccent)
.image(R.mipmap.ic_launcher)
.title(getString(R.string.intro_hello_title))
.description(getString(R.string.intro_hello_message))
.build())
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
addSlide(SlideFragmentBuilder()
.backgroundColor(R.color.colorAccent)
.buttonsColor(R.color.colorPrimary)
.image(R.drawable.ic_info_outline_white_48dp)
.title(getString(R.string.intro_needs_selfoss_title))
.description(getString(R.string.intro_needs_selfoss_message))
.build(),
MessageButtonBehaviour(View.OnClickListener {
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://selfoss.aditu.de"))
startActivity(browserIntent)
}, getString(R.string.intro_needs_selfoss_link)))
addSlide(
SlideFragmentBuilder()
.backgroundColor(R.color.colorPrimary)
.buttonsColor(R.color.colorAccent)
.image(R.drawable.web_hi_res_512)
.title(getString(R.string.intro_hello_title))
.description(getString(R.string.intro_hello_message))
.build()
)
addSlide(SlideFragmentBuilder()
.backgroundColor(R.color.colorPrimaryDark)
.buttonsColor(R.color.colorAccentDark)
.image(R.drawable.ic_thumb_up_white_48dp)
.title(getString(R.string.intro_all_set_title))
.description(getString(R.string.intro_all_set_message))
.build())
addSlide(
SlideFragmentBuilder()
.backgroundColor(R.color.colorAccent)
.buttonsColor(R.color.colorPrimary)
.image(R.drawable.ic_info_outline_white_48px)
.title(getString(R.string.intro_needs_selfoss_title))
.description(getString(R.string.intro_needs_selfoss_message))
.build(),
MessageButtonBehaviour(
View.OnClickListener {
val browserIntent = Intent(
Intent.ACTION_VIEW,
Uri.parse("https://selfoss.aditu.de")
)
startActivity(browserIntent)
}, getString(R.string.intro_needs_selfoss_link)
)
)
addSlide(
SlideFragmentBuilder()
.backgroundColor(R.color.colorPrimaryDark)
.buttonsColor(R.color.colorAccentDark)
.image(R.drawable.ic_thumb_up_white_48px)
.title(getString(R.string.intro_all_set_title))
.description(getString(R.string.intro_all_set_message))
.build()
)
}
override fun onFinish() {

View File

@ -6,122 +6,117 @@ import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.support.design.widget.TextInputLayout
import android.support.v7.app.AlertDialog
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
import android.text.TextUtils
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.inputmethod.EditorInfo
import android.widget.Button
import android.widget.EditText
import android.widget.Switch
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.mikepenz.aboutlibraries.Libs
import com.mikepenz.aboutlibraries.LibsBuilder
import kotlinx.android.synthetic.main.activity_login.*
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
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.isBaseUrlValid
import com.ftinc.scoop.Scoop
class LoginActivity : AppCompatActivity() {
private var inValidCount: Int = 0
private var isWithSelfSignedCert = false
private var isWithLogin = false
private var isWithHTTPLogin = false
private lateinit var settings: SharedPreferences
private lateinit var mFirebaseAnalytics: FirebaseAnalytics
private lateinit var mUrlView: EditText
private lateinit var mLoginView: TextView
private lateinit var mHTTPLoginView: TextView
private lateinit var mProgressView: View
private lateinit var mPasswordView: EditText
private lateinit var mHTTPPasswordView: EditText
private lateinit var mLoginFormView: View
private lateinit var editor: SharedPreferences.Editor
private lateinit var firebaseAnalytics: FirebaseAnalytics
private lateinit var userIdentifier: String
private var logErrors: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Scoop.getInstance().apply(this)
setContentView(R.layout.activity_login)
val toolbar: Toolbar = findViewById(R.id.toolbar)
setSupportActionBar(toolbar)
handleBaseUrlFail()
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()) {
goToMain()
}
firebaseAnalytics = FirebaseAnalytics.getInstance(this)
handleActions()
}
private fun handleActions() {
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()
return@OnEditorActionListener true
}
false
}
)
signInButton.setOnClickListener { attemptLogin() }
withLogin.setOnCheckedChangeListener { _, b ->
isWithLogin = !isWithLogin
val visi: Int = if (b) View.VISIBLE else View.GONE
loginLayout.visibility = visi
passwordLayout.visibility = visi
}
withHttpLogin.setOnCheckedChangeListener { _, b ->
isWithHTTPLogin = !isWithHTTPLogin
val visi: Int = if (b) View.VISIBLE else View.GONE
httpLoginInput.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.BUTTON_NEUTRAL,
"OK",
{ dialog, _ -> dialog.dismiss() }
)
alertDialog.show()
}
settings = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
if (settings.getString("url", "").isNotEmpty()) {
goToMain()
} else {
this@LoginActivity.checkAndDisplayStoreApk()
}
mFirebaseAnalytics = 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)
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, _ ->
if (id == R.id.login || id == EditorInfo.IME_NULL) {
attemptLogin()
return@OnEditorActionListener true
}
false
})
mEmailSignInButton.setOnClickListener { attemptLogin() }
mSwitch.setOnCheckedChangeListener { _, b ->
isWithLogin = !isWithLogin
val visi: Int = if (b) View.VISIBLE else View.GONE
mLoginLayout.visibility = visi
mPasswordLayout.visibility = visi
}
mHTTPSwitch.setOnCheckedChangeListener { _, b ->
isWithHTTPLogin = !isWithHTTPLogin
val visi: Int = if (b) View.VISIBLE else View.GONE
mHTTPLoginLayout.visibility = visi
mHTTPPasswordLayout.visibility = visi
}
}
private fun goToMain() {
@ -133,25 +128,25 @@ class LoginActivity : AppCompatActivity() {
private fun attemptLogin() {
// Reset errors.
mUrlView.error = null
mLoginView.error = null
mHTTPLoginView.error = null
mPasswordView.error = null
mHTTPPasswordView.error = null
urlView.error = null
loginView.error = null
httpLoginView.error = null
passwordView.error = null
httpPasswordView.error = null
// Store values at the time of the login attempt.
val url = mUrlView.text.toString()
val login = mLoginView.text.toString()
val httpLogin = mHTTPLoginView.text.toString()
val password = mPasswordView.text.toString()
val httpPassword = mHTTPPasswordView.text.toString()
val url = urlView.text.toString()
val login = loginView.text.toString()
val httpLogin = httpLoginView.text.toString()
val password = passwordView.text.toString()
val httpPassword = httpPasswordView.text.toString()
var cancel = false
var focusView: View? = null
if (!url.isBaseUrlValid()) {
mUrlView.error = getString(R.string.login_url_problem)
focusView = mUrlView
urlView.error = getString(R.string.login_url_problem)
focusView = urlView
cancel = true
inValidCount++
if (inValidCount == 3) {
@ -159,9 +154,10 @@ class LoginActivity : AppCompatActivity() {
alertDialog.setTitle(getString(R.string.warning_wrong_url))
alertDialog.setMessage(getString(R.string.text_wrong_url))
alertDialog.setButton(
AlertDialog.BUTTON_NEUTRAL,
"OK",
{ dialog, _ -> dialog.dismiss() })
AlertDialog.BUTTON_NEUTRAL,
"OK",
{ dialog, _ -> dialog.dismiss() }
)
alertDialog.show()
inValidCount = 0
}
@ -169,14 +165,14 @@ class LoginActivity : AppCompatActivity() {
if (isWithLogin || isWithHTTPLogin) {
if (TextUtils.isEmpty(password)) {
mPasswordView.error = getString(R.string.error_invalid_password)
focusView = mPasswordView
passwordView.error = getString(R.string.error_invalid_password)
focusView = passwordView
cancel = true
}
if (TextUtils.isEmpty(login)) {
mLoginView.error = getString(R.string.error_field_required)
focusView = mLoginView
loginView.error = getString(R.string.error_field_required)
focusView = loginView
cancel = true
}
}
@ -186,80 +182,98 @@ class LoginActivity : AppCompatActivity() {
} else {
showProgress(true)
val editor = settings.edit()
editor.putString("url", url)
editor.putString("login", login)
editor.putString("httpUserName", httpLogin)
editor.putString("password", password)
editor.putString("httpPassword", httpPassword)
editor.putBoolean("isSelfSignedCert", isWithSelfSignedCert)
editor.apply()
val api = SelfossApi(this, this@LoginActivity)
val api = SelfossApi(
this,
this@LoginActivity,
isWithSelfSignedCert,
isWithSelfSignedCert
)
api.login().enqueue(object : Callback<SuccessResponse> {
private fun preferenceError() {
private fun preferenceError(t: Throwable) {
editor.remove("url")
editor.remove("login")
editor.remove("httpUserName")
editor.remove("password")
editor.remove("httpPassword")
editor.apply()
mUrlView.error = getString(R.string.wrong_infos)
mLoginView.error = getString(R.string.wrong_infos)
mPasswordView.error = getString(R.string.wrong_infos)
mHTTPLoginView.error = getString(R.string.wrong_infos)
mHTTPPasswordView.error = getString(R.string.wrong_infos)
urlView.error = getString(R.string.wrong_infos)
loginView.error = getString(R.string.wrong_infos)
passwordView.error = getString(R.string.wrong_infos)
httpLoginView.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)
}
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) {
mFirebaseAnalytics.logEvent(FirebaseAnalytics.Event.LOGIN, Bundle())
firebaseAnalytics.logEvent(FirebaseAnalytics.Event.LOGIN, Bundle())
goToMain()
} else {
preferenceError()
preferenceError(Exception("No response body..."))
}
}
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) {
val shortAnimTime = resources.getInteger(android.R.integer.config_shortAnimTime)
mLoginFormView.visibility = if (show) View.GONE else View.VISIBLE
mLoginFormView
.animate()
.setDuration(shortAnimTime.toLong())
.alpha(
if (show) 0F else 1F
).setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
mLoginFormView.visibility = if (show) View.GONE else View.VISIBLE
loginForm.visibility = if (show) View.GONE else View.VISIBLE
loginForm
.animate()
.setDuration(shortAnimTime.toLong())
.alpha(
if (show) 0F else 1F
).setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
loginForm.visibility = if (show) View.GONE else View.VISIBLE
}
}
})
)
mProgressView.visibility = if (show) View.VISIBLE else View.GONE
mProgressView
.animate()
.setDuration(shortAnimTime.toLong())
.alpha(
if (show) 1F else 0F
).setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
mProgressView.visibility = if (show) View.VISIBLE else View.GONE
loginProgress.visibility = if (show) View.VISIBLE else View.GONE
loginProgress
.animate()
.setDuration(shortAnimTime.toLong())
.alpha(
if (show) 1F else 0F
).setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
loginProgress.visibility = if (show) View.VISIBLE else View.GONE
}
}
})
)
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.login_menu, menu)
menu.findItem(R.id.login_debug).isChecked = logErrors
return true
}
@ -267,10 +281,18 @@ class LoginActivity : AppCompatActivity() {
when (item.itemId) {
R.id.about -> {
LibsBuilder()
.withActivityStyle(Libs.ActivityStyle.LIGHT_DARK_TOOLBAR)
.withAboutIconShown(true)
.withAboutVersionShown(true)
.start(this)
.withActivityStyle(Libs.ActivityStyle.LIGHT_DARK_TOOLBAR)
.withAboutIconShown(true)
.withAboutVersionShown(true)
.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
}
else -> return super.onOptionsItemSelected(item)

View File

@ -5,15 +5,16 @@ import android.os.Bundle
import android.preference.PreferenceManager
import android.support.v7.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (PreferenceManager.getDefaultSharedPreferences(baseContext).getBoolean("firstStart", true)) {
if (PreferenceManager.getDefaultSharedPreferences(baseContext).getBoolean(
"firstStart",
true
)) {
val i = Intent(this@MainActivity, IntroActivity::class.java)
startActivity(i)
} else {
@ -22,6 +23,5 @@ class MainActivity : AppCompatActivity() {
}
finish()
}
}

View File

@ -6,37 +6,50 @@ import android.net.Uri
import android.preference.PreferenceManager
import android.support.multidex.MultiDexApplication
import android.widget.ImageView
import apps.amine.bou.readerforselfoss.utils.Config
import com.anupcowkur.reservoir.Reservoir
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import com.crashlytics.android.Crashlytics
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.mikepenz.materialdrawer.util.AbstractDrawerImageLoader
import com.mikepenz.materialdrawer.util.DrawerImageLoader
import io.fabric.sdk.android.Fabric
import java.io.IOException
import java.util.UUID.randomUUID
class MyApp : MultiDexApplication() {
override fun 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)
.setFeedbackEmailAddress(getString(R.string.feedback_email))
.applyAllDefaultRules()
.setPositiveFeedbackCollectors(GooglePlayStoreFeedbackCollector())
.setCriticalFeedbackCollectors(DefaultEmailFeedbackCollector(BuildConfig.FEEDBACK_EMAIL))
.applyAllDefaultRules()
}
private fun initCache() {
@ -49,12 +62,20 @@ class MyApp : MultiDexApplication() {
private fun initDrawerImageLoader() {
DrawerImageLoader.init(object : AbstractDrawerImageLoader() {
override fun set(imageView: ImageView?, uri: Uri?, placeholder: Drawable?, tag: String?) {
Glide.with(imageView?.context).load(uri).placeholder(placeholder).into(imageView)
override fun set(
imageView: ImageView?,
uri: Uri?,
placeholder: Drawable?,
tag: String?
) {
Glide.with(imageView?.context)
.load(uri)
.apply(RequestOptions.fitCenterTransform().placeholder(placeholder))
.into(imageView)
}
override fun cancel(imageView: ImageView?) {
Glide.clear(imageView)
Glide.with(imageView?.context).clear(imageView)
}
override fun placeholder(ctx: Context?, tag: String?): Drawable {
@ -65,21 +86,35 @@ class MyApp : MultiDexApplication() {
private fun initTheme() {
Scoop.waffleCone()
.addFlavor(getString(R.string.default_theme), R.style.NoBar, true)
.addFlavor(getString(R.string.default_dark_theme), R.style.NoBarDark)
.addFlavor(getString(R.string.teal_orange_theme), R.style.NoBarTealOrange)
.addFlavor(getString(R.string.teal_orange_dark_theme), R.style.NoBarTealOrangeDark)
.addFlavor(getString(R.string.cyan_pink_theme), R.style.NoBarCyanPink)
.addFlavor(getString(R.string.cyan_pink_dark_theme), R.style.NoBarCyanPinkDark)
.addFlavor(getString(R.string.grey_orange_theme), R.style.NoBarGreyOrange)
.addFlavor(getString(R.string.grey_orange_dark_theme), R.style.NoBarGreyOrangeDark)
.addFlavor(getString(R.string.blue_amber_theme), R.style.NoBarBlueAmber)
.addFlavor(getString(R.string.blue_amber_dark_theme), R.style.NoBarBlueAmberDark)
.addFlavor(getString(R.string.indigo_pink_theme), R.style.NoBarIndigoPink)
.addFlavor(getString(R.string.indigo_pink_dark_theme), R.style.NoBarIndigoPinkDark)
.addFlavor(getString(R.string.red_teal_theme), R.style.NoBarRedTeal)
.addFlavor(getString(R.string.red_teal_dark_theme), R.style.NoBarRedTealDark)
.setSharedPreferences(PreferenceManager.getDefaultSharedPreferences(this))
.initialize()
.addFlavor(getString(R.string.default_theme), R.style.NoBar, true)
.addFlavor(getString(R.string.default_dark_theme), R.style.NoBarDark)
.addFlavor(getString(R.string.teal_orange_theme), R.style.NoBarTealOrange)
.addFlavor(getString(R.string.teal_orange_dark_theme), R.style.NoBarTealOrangeDark)
.addFlavor(getString(R.string.cyan_pink_theme), R.style.NoBarCyanPink)
.addFlavor(getString(R.string.cyan_pink_dark_theme), R.style.NoBarCyanPinkDark)
.addFlavor(getString(R.string.grey_orange_theme), R.style.NoBarGreyOrange)
.addFlavor(getString(R.string.grey_orange_dark_theme), R.style.NoBarGreyOrangeDark)
.addFlavor(getString(R.string.blue_amber_theme), R.style.NoBarBlueAmber)
.addFlavor(getString(R.string.blue_amber_dark_theme), R.style.NoBarBlueAmberDark)
.addFlavor(getString(R.string.indigo_pink_theme), R.style.NoBarIndigoPink)
.addFlavor(getString(R.string.indigo_pink_dark_theme), R.style.NoBarIndigoPinkDark)
.addFlavor(getString(R.string.red_teal_theme), R.style.NoBarRedTeal)
.addFlavor(getString(R.string.red_teal_dark_theme), R.style.NoBarRedTealDark)
.setSharedPreferences(PreferenceManager.getDefaultSharedPreferences(this))
.initialize()
}
private fun tryToHandleBug() {
val oldHandler = Thread.getDefaultUncaughtExceptionHandler()
Thread.setDefaultUncaughtExceptionHandler { thread, e ->
if (e is java.lang.NoClassDefFoundError && e.stackTrace.asList().any {
it.toString().contains("android.view.ViewDebug")
}) {
Unit
} else {
oldHandler.uncaughtException(thread, e)
}
}
}
}

View File

@ -1,115 +1,209 @@
package apps.amine.bou.readerforselfoss
import android.content.Intent
import android.net.Uri
import android.content.SharedPreferences
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.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.ParsedContent
import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent
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.shareLink
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import com.crashlytics.android.Crashlytics
import com.ftinc.scoop.Scoop
import com.github.rubensousa.floatingtoolbar.FloatingToolbar
import kotlinx.android.synthetic.main.activity_reader.*
import org.sufficientlysecure.htmltextview.HtmlHttpImageGetter
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class ReaderActivity : DragDismissActivity() {
class ReaderActivity : AppCompatActivity() {
private lateinit var mCustomTabActivityHelper: CustomTabActivityHelper
override fun onStart() {
super.onStart()
mCustomTabActivityHelper.bindCustomTabsService(this)
}
//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 onStop() {
super.onStop()
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)
val v = inflater.inflate(R.layout.activity_reader, parent, false)
showProgressBar()
setContentView(R.layout.activity_reader)
val image: ImageView = v.findViewById(R.id.imageView)
val source: TextView = v.findViewById(R.id.source)
val title: TextView = v.findViewById(R.id.title)
val content: HtmlTextView = v.findViewById(R.id.content)
val url = intent.getStringExtra("url")
val parser = MercuryApi(getString(R.string.mercury))
val browserBtn: ImageButton = v.findViewById(R.id.browserBtn)
val shareBtn: ImageButton = v.findViewById(R.id.shareBtn)
url = intent.getStringExtra("url")
contentText = intent.getStringExtra("content")
contentTitle = intent.getStringExtra("title")
contentImage = intent.getStringExtra("image")
contentSource = intent.getStringExtra("source")
fab = findViewById(R.id.fab)
val mFloatingToolbar: FloatingToolbar = findViewById(R.id.floatingToolbar)
mFloatingToolbar.attachFab(fab)
val customTabsIntent = this@ReaderActivity.buildCustomTabsIntent()
mCustomTabActivityHelper = CustomTabActivityHelper()
mCustomTabActivityHelper.bindCustomTabsService(this)
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
parser.parseUrl(url).enqueue(object : Callback<ParsedContent> {
override fun onResponse(call: Call<ParsedContent>, response: Response<ParsedContent>) {
if (response.body() != null && response.body()!!.content != null && response.body()!!.content.isNotEmpty()) {
source.text = response.body()!!.domain
title.text = response.body()!!.title
if (response.body()!!.content != null && !response.body()!!.content.isEmpty()) {
try {
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())
Glide
.with(baseContext)
.load(response.body()!!.lead_image_url)
.asBitmap()
.fitCenter()
.into(image)
shareBtn.setOnClickListener {
this@ReaderActivity.shareLink(response.body()!!.url)
}
browserBtn.setOnClickListener {
this@ReaderActivity.openItemUrl(
response.body()!!.url,
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)
}
hideProgressBar()
} else openInBrowserAfterFailing()
this@ReaderActivity
)
else -> Unit
}
}
override fun onFailure(call: Call<ParsedContent>, t: Throwable) = openInBrowserAfterFailing()
private fun openInBrowserAfterFailing() {
this@ReaderActivity.openItemUrl(
url,
customTabsIntent,
true,
false,
this@ReaderActivity
)
finish()
override fun onItemLongClick(item: MenuItem?) {
}
})
return v
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> {
override fun onResponse(
call: Call<ParsedContent>,
response: Response<ParsedContent>
) {
if (response.body() != null && response.body()!!.content != null && response.body()!!.content.isNotEmpty()) {
source.text = response.body()!!.domain
titleView.text = response.body()!!.title
this@ReaderActivity.url = response.body()!!.url
if (response.body()!!.content != null && !response.body()!!.content.isEmpty()) {
tryToHandleHtml(response.body()!!.content, customTabsIntent, prefs)
}
if (response.body()!!.lead_image_url != null && !response.body()!!.lead_image_url.isEmpty()) {
imageView.visibility = View.VISIBLE
Glide
.with(baseContext)
.asBitmap()
.load(response.body()!!.lead_image_url)
.apply(RequestOptions.fitCenterTransform())
.into(imageView)
} else {
imageView.visibility = View.GONE
}
nestedScrollView.scrollTo(0, 0)
progressBar.visibility = View.GONE
} else {
openInBrowserAfterFailing(customTabsIntent)
}
}
override fun onFailure(
call: Call<ParsedContent>,
t: Throwable
) = openInBrowserAfterFailing(customTabsIntent)
})
}
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,22 +2,18 @@ package apps.amine.bou.readerforselfoss
import android.content.Intent
import android.os.Bundle
import android.preference.PreferenceManager
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.Toolbar
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.api.selfoss.SelfossApi
import apps.amine.bou.readerforselfoss.api.selfoss.Sources
import com.ftinc.scoop.Scoop
import kotlinx.android.synthetic.main.activity_sources.*
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class SourcesActivity : AppCompatActivity() {
@ -25,41 +21,64 @@ class SourcesActivity : AppCompatActivity() {
super.onCreate(savedInstanceState)
Scoop.getInstance().apply(this)
setContentView(R.layout.activity_sources)
val toolbar: Toolbar = findViewById(R.id.toolbar)
setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
}
override fun onStop() {
super.onStop()
recyclerView.clearOnScrollListeners()
}
override fun onResume() {
super.onResume()
val mFab: FloatingActionButton = findViewById(R.id.fab)
val mRecyclerView: RecyclerView = findViewById(R.id.activity_sources)
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()
mFab.attachToRecyclerView(mRecyclerView)
mRecyclerView.setHasFixedSize(true)
mRecyclerView.layoutManager = mLayoutManager
recyclerView.setHasFixedSize(true)
recyclerView.layoutManager = mLayoutManager
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>>
) {
if (response.body() != null && response.body()!!.isNotEmpty()) {
items = response.body() as ArrayList<Sources>
}
val mAdapter = SourcesListAdapter(this@SourcesActivity, items, api)
mRecyclerView.adapter = mAdapter
recyclerView.adapter = mAdapter
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()
}
}
override fun onFailure(call: Call<List<Sources>>, t: Throwable) {
Toast.makeText(this@SourcesActivity, R.string.cant_get_sources, Toast.LENGTH_SHORT).show()
Toast.makeText(
this@SourcesActivity,
R.string.cant_get_sources,
Toast.LENGTH_SHORT
).show()
}
})
mFab.setOnClickListener {
fab.setOnClickListener {
startActivity(Intent(this@SourcesActivity, AddSourceActivity::class.java))
}
}

View File

@ -2,57 +2,59 @@ package apps.amine.bou.readerforselfoss.adapters
import android.app.Activity
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Color
import android.support.constraint.ConstraintLayout
import android.support.design.widget.Snackbar
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory
import android.support.v7.widget.CardView
import android.support.v7.widget.RecyclerView
import android.text.Html
import android.text.format.DateUtils
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.ImageView.ScaleType
import android.widget.TextView
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.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.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 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 kotlinx.android.synthetic.main.card_item.view.*
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class ItemCardAdapter(private val app: Activity,
private val items: ArrayList<Item>,
private val api: SelfossApi,
private val helper: CustomTabActivityHelper,
private val internalBrowser: Boolean,
private val articleViewer: Boolean,
private val fullHeightCards: Boolean,
private val appColors: AppColors) : RecyclerView.Adapter<ItemCardAdapter.ViewHolder>() {
class ItemCardAdapter(
private val app: Activity,
private val items: ArrayList<Item>,
private val api: SelfossApi,
private val helper: CustomTabActivityHelper,
private val internalBrowser: Boolean,
private val articleViewer: Boolean,
private val fullHeightCards: Boolean,
private val appColors: AppColors,
val debugReadingItems: Boolean,
val userIdentifier: String
) : RecyclerView.Adapter<ItemCardAdapter.ViewHolder>() {
private val c: Context = app.baseContext
private val generator: ColorGenerator = ColorGenerator.MATERIAL
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)
}
@ -60,37 +62,32 @@ class ItemCardAdapter(private val app: Activity,
val itm = items[position]
holder.saveBtn.isLiked = itm.starred
holder.title.text = Html.fromHtml(itm.title)
holder.mView.favButton.isLiked = itm.starred
holder.mView.title.text = Html.fromHtml(itm.title)
holder.sourceTitleAndDate.text = itm.sourceAndDateText()
holder.mView.sourceTitleAndDate.text = itm.sourceAndDateText()
if (itm.getThumbnail(c).isEmpty()) {
Glide.clear(holder.itemImage)
holder.itemImage.setImageDrawable(null)
Glide.with(c).clear(holder.mView.itemImage)
holder.mView.itemImage.setImageDrawable(null)
} else {
if (fullHeightCards) {
c.bitmapFitCenter(itm.getThumbnail(c), holder.itemImage)
} else {
c.bitmapCenterCrop(itm.getThumbnail(c), holder.itemImage)
}
c.bitmapCenterCrop(itm.getThumbnail(c), holder.mView.itemImage)
}
val fHolder = holder
if (itm.getIcon(c).isEmpty()) {
val color = generator.getColor(itm.sourcetitle)
val drawable =
TextDrawable
.builder()
.round()
.build(itm.sourcetitle.toTextDrawableString(), color)
holder.sourceImage.setImageDrawable(drawable)
TextDrawable
.builder()
.round()
.build(itm.sourcetitle.toTextDrawableString(), color)
holder.mView.sourceImage.setImageDrawable(drawable)
} 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 {
@ -99,13 +96,21 @@ class ItemCardAdapter(private val app: Activity,
private fun doUnmark(i: Item, position: Int) {
val s = Snackbar
.make(app.findViewById(R.id.coordLayout), R.string.marked_as_read, Snackbar.LENGTH_LONG)
.make(
app.findViewById(R.id.coordLayout),
R.string.marked_as_read,
Snackbar.LENGTH_LONG
)
.setAction(R.string.undo_string) {
items.add(position, i)
notifyItemInserted(position)
api.unmarkItem(i.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) {
items.remove(i)
@ -129,58 +134,80 @@ class ItemCardAdapter(private val app: Activity,
notifyItemRemoved(position)
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)
}
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
Toast.makeText(app, app.getString(R.string.cant_mark_read), Toast.LENGTH_SHORT).show()
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()
items.add(i)
notifyItemInserted(position)
}
})
}
inner class ViewHolder(val mView: ConstraintLayout) : 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
inner class ViewHolder(val mView: CardView) : RecyclerView.ViewHolder(mView) {
init {
(mView.findViewById<CardView>(R.id.card)).setCardBackgroundColor(appColors.cardBackground)
mView.setCardBackgroundColor(appColors.cardBackground)
handleClickListeners()
handleCustomTabActions()
}
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) {
itemImage.maxHeight = c.resources.getDimension(R.dimen.card_image_max_height).toInt()
itemImage.scaleType = ScaleType.CENTER_CROP
mView.itemImage.maxHeight = c.resources.getDimension(R.dimen.card_image_max_height).toInt()
mView.itemImage.scaleType = ScaleType.CENTER_CROP
}
saveBtn.setOnLikeListener(object : OnLikeListener {
mView.favButton.setOnLikeListener(object : OnLikeListener {
override fun liked(likeButton: LikeButton) {
val (id) = items[adapterPosition]
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) {
saveBtn.isLiked = false
Toast.makeText(c, R.string.cant_mark_favortie, Toast.LENGTH_SHORT).show()
override fun onFailure(
call: Call<SuccessResponse>,
t: Throwable
) {
mView.favButton.isLiked = false
Toast.makeText(
c,
R.string.cant_mark_favortie,
Toast.LENGTH_SHORT
).show()
}
})
}
@ -188,22 +215,33 @@ class ItemCardAdapter(private val app: Activity,
override fun unLiked(likeButton: LikeButton) {
val (id) = items[adapterPosition]
api.unstarrItem(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) {
saveBtn.isLiked = true
Toast.makeText(c, R.string.cant_unmark_favortie, Toast.LENGTH_SHORT).show()
override fun onFailure(
call: Call<SuccessResponse>,
t: Throwable
) {
mView.favButton.isLiked = true
Toast.makeText(
c,
R.string.cant_unmark_favortie,
Toast.LENGTH_SHORT
).show()
}
})
}
})
shareBtn.setOnClickListener {
mView.shareBtn.setOnClickListener {
c.shareLink(items[adapterPosition].getLinkDecoded())
}
browserBtn.setOnClickListener {
c.openInBrowser(items[adapterPosition])
mView.browserBtn.setOnClickListener {
c.openInBrowserAsNewTask(items[adapterPosition])
}
}
@ -212,11 +250,17 @@ class ItemCardAdapter(private val app: Activity,
helper.bindCustomTabsService(app)
mView.setOnClickListener {
c.openItemUrl(items[adapterPosition].getLinkDecoded(),
customTabsIntent,
internalBrowser,
articleViewer,
app)
c.openItemUrl(
items[adapterPosition].getLinkDecoded(),
items[adapterPosition].content,
items[adapterPosition].getThumbnail(c),
items[adapterPosition].title,
items[adapterPosition].sourceAndDateText(),
customTabsIntent,
internalBrowser,
articleViewer,
app
)
}
}
}

View File

@ -1,59 +1,64 @@
package apps.amine.bou.readerforselfoss.adapters
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Color
import android.net.Uri
import android.support.constraint.ConstraintLayout
import android.support.design.widget.Snackbar
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory
import android.support.v7.widget.RecyclerView
import android.text.Html
import android.text.format.DateUtils
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.*
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 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.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.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.util.ColorGenerator
import com.crashlytics.android.Crashlytics
import com.like.LikeButton
import com.like.OnLikeListener
import kotlinx.android.synthetic.main.list_item.view.*
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import java.util.*
import kotlin.collections.ArrayList
class ItemListAdapter(private val app: Activity,
private val items: ArrayList<Item>,
private val api: SelfossApi,
private val helper: CustomTabActivityHelper,
private val clickBehavior: Boolean,
private val internalBrowser: Boolean,
private val articleViewer: Boolean) : RecyclerView.Adapter<ItemListAdapter.ViewHolder>() {
class ItemListAdapter(
private val app: Activity,
private val items: ArrayList<Item>,
private val api: SelfossApi,
private val helper: CustomTabActivityHelper,
private val clickBehavior: Boolean,
private val internalBrowser: Boolean,
private val articleViewer: Boolean,
val debugReadingItems: Boolean,
val userIdentifier: String
) : RecyclerView.Adapter<ItemListAdapter.ViewHolder>() {
private val generator: ColorGenerator = ColorGenerator.MATERIAL
private val c: Context = app.baseContext
private val bars: ArrayList<Boolean> = ArrayList(Collections.nCopies(items.size + 1, false))
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(c).inflate(R.layout.list_item, parent, false) as ConstraintLayout
val v = LayoutInflater.from(c).inflate(
R.layout.list_item,
parent,
false
) as ConstraintLayout
return ViewHolder(v)
}
@ -61,27 +66,29 @@ class ItemListAdapter(private val app: Activity,
val itm = items[position]
holder.saveBtn.isLiked = itm.starred
holder.title.text = Html.fromHtml(itm.title)
holder.mView.favButton.isLiked = itm.starred
holder.mView.title.text = Html.fromHtml(itm.title)
holder.sourceTitleAndDate.text = itm.sourceAndDateText()
holder.mView.sourceTitleAndDate.text = itm.sourceAndDateText()
if (itm.getThumbnail(c).isEmpty()) {
val sizeInInt = 46
val sizeInDp = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, sizeInInt.toFloat(), c.resources
.displayMetrics).toInt()
TypedValue.COMPLEX_UNIT_DIP, sizeInInt.toFloat(), c.resources
.displayMetrics
).toInt()
val marginInInt = 16
val marginInDp = TypedValue.applyDimension(
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.width = sizeInDp
params.setMargins(marginInDp, 0, 0, 0)
holder.sourceImage.layoutParams = params
holder.mView.itemImage.layoutParams = params
if (itm.getIcon(c).isEmpty()) {
val color = generator.getColor(itm.sourcetitle)
@ -93,31 +100,42 @@ class ItemListAdapter(private val app: Activity,
val builder = TextDrawable.builder().round()
val drawable = builder.build(textDrawable.toString(), color)
holder.sourceImage.setImageDrawable(drawable)
holder.mView.itemImage.setImageDrawable(drawable)
} else {
c.circularBitmapDrawable(itm.getIcon(c), holder.sourceImage)
c.circularBitmapDrawable(itm.getIcon(c), holder.mView.itemImage)
}
} 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
private fun doUnmark(i: Item, position: Int) {
val s = Snackbar
.make(app.findViewById(R.id.coordLayout), R.string.marked_as_read, Snackbar.LENGTH_LONG)
.make(
app.findViewById(R.id.coordLayout),
R.string.marked_as_read,
Snackbar.LENGTH_LONG
)
.setAction(R.string.undo_string) {
items.add(position, i)
notifyItemInserted(position)
api.unmarkItem(i.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) {
items.remove(i)
@ -141,28 +159,46 @@ class ItemListAdapter(private val app: Activity,
notifyItemRemoved(position)
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)
}
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
Toast.makeText(app, app.getString(R.string.cant_mark_read), Toast.LENGTH_SHORT).show()
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()
items.add(i)
notifyItemInserted(position)
}
})
}
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 {
handleClickListeners()
@ -170,24 +206,27 @@ class ItemListAdapter(private val app: Activity,
}
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)
saveBtn.setOnLikeListener(object : OnLikeListener {
mView.favButton.setOnLikeListener(object : OnLikeListener {
override fun liked(likeButton: LikeButton) {
val (id) = items[adapterPosition]
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) {
saveBtn.isLiked = false
Toast.makeText(c, R.string.cant_mark_favortie, Toast.LENGTH_SHORT).show()
override fun onFailure(
call: Call<SuccessResponse>,
t: Throwable
) {
mView.favButton.isLiked = false
Toast.makeText(
c,
R.string.cant_mark_favortie,
Toast.LENGTH_SHORT
).show()
}
})
}
@ -195,27 +234,37 @@ class ItemListAdapter(private val app: Activity,
override fun unLiked(likeButton: LikeButton) {
val (id) = items[adapterPosition]
api.unstarrItem(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) {
saveBtn.isLiked = true
Toast.makeText(c, R.string.cant_unmark_favortie, Toast.LENGTH_SHORT).show()
override fun onFailure(
call: Call<SuccessResponse>,
t: Throwable
) {
mView.favButton.isLiked = true
Toast.makeText(
c,
R.string.cant_unmark_favortie,
Toast.LENGTH_SHORT
).show()
}
})
}
})
shareBtn.setOnClickListener {
mView.shareBtn.setOnClickListener {
c.shareLink(items[adapterPosition].getLinkDecoded())
}
browserBtn.setOnClickListener {
c.openInBrowser(items[adapterPosition])
mView.browserBtn.setOnClickListener {
c.openInBrowserAsNewTask(items[adapterPosition])
}
}
private fun handleCustomTabActions() {
val customTabsIntent = c.buildCustomTabsIntent()
helper.bindCustomTabsService(app)
@ -223,11 +272,17 @@ class ItemListAdapter(private val app: Activity,
if (!clickBehavior) {
mView.setOnClickListener {
c.openItemUrl(items[adapterPosition].getLinkDecoded(),
customTabsIntent,
internalBrowser,
articleViewer,
app)
c.openItemUrl(
items[adapterPosition].getLinkDecoded(),
items[adapterPosition].content,
items[adapterPosition].getThumbnail(c),
items[adapterPosition].title,
items[adapterPosition].sourceAndDateText(),
customTabsIntent,
internalBrowser,
articleViewer,
app
)
}
mView.setOnLongClickListener {
actionBarShowHide()
@ -236,11 +291,17 @@ class ItemListAdapter(private val app: Activity,
} else {
mView.setOnClickListener { actionBarShowHide() }
mView.setOnLongClickListener {
c.openItemUrl(items[adapterPosition].getLinkDecoded(),
customTabsIntent,
internalBrowser,
articleViewer,
app)
c.openItemUrl(
items[adapterPosition].getLinkDecoded(),
items[adapterPosition].content,
items[adapterPosition].getThumbnail(c),
items[adapterPosition].title,
items[adapterPosition].sourceAndDateText(),
customTabsIntent,
internalBrowser,
articleViewer,
app
)
true
}
}
@ -248,7 +309,11 @@ class ItemListAdapter(private val app: Activity,
private fun actionBarShowHide() {
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

@ -7,32 +7,34 @@ import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import com.amulyakhare.textdrawable.TextDrawable
import com.amulyakhare.textdrawable.util.ColorGenerator
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
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.glide.circularBitmapDrawable
import apps.amine.bou.readerforselfoss.utils.toTextDrawableString
import com.amulyakhare.textdrawable.TextDrawable
import com.amulyakhare.textdrawable.util.ColorGenerator
import kotlinx.android.synthetic.main.source_list_item.view.*
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class SourcesListAdapter(private val app: Activity,
private val items: ArrayList<Sources>,
private val api: SelfossApi) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>() {
class SourcesListAdapter(
private val app: Activity,
private val items: ArrayList<Sources>,
private val api: SelfossApi
) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>() {
private val c: Context = app.baseContext
private val generator: ColorGenerator = ColorGenerator.MATERIAL
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(c).inflate(R.layout.source_list_item, parent, false) as ConstraintLayout
val v = LayoutInflater.from(c).inflate(
R.layout.source_list_item,
parent,
false
) as ConstraintLayout
return ViewHolder(v)
}
@ -43,57 +45,59 @@ class SourcesListAdapter(private val app: Activity,
val color = generator.getColor(itm.title)
val drawable =
TextDrawable
.builder()
.round()
.build(itm.title.toTextDrawableString(), color)
holder.sourceImage.setImageDrawable(drawable)
TextDrawable
.builder()
.round()
.build(itm.title.toTextDrawableString(), color)
holder.mView.itemImage.setImageDrawable(drawable)
} else {
c.circularBitmapDrawable(itm.getIcon(c), holder.sourceImage)
c.circularBitmapDrawable(itm.getIcon(c), holder.mView.itemImage)
}
holder.sourceTitle.text = itm.title
holder.mView.sourceTitle.text = itm.title
}
override fun getItemCount(): Int {
return items.size
}
override fun getItemCount(): Int = items.size
inner class ViewHolder(internal val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) {
lateinit var sourceImage: ImageView
lateinit var sourceTitle: TextView
init {
handleClickListeners()
}
private fun handleClickListeners() {
sourceImage = mView.findViewById(R.id.itemImage)
sourceTitle = mView.findViewById(R.id.sourceTitle)
val deleteBtn: Button = mView.findViewById(R.id.deleteBtn)
deleteBtn.setOnClickListener {
val (id) = items[adapterPosition]
api.deleteSource(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.body() != null && response.body()!!.isSuccess) {
items.removeAt(adapterPosition)
notifyItemRemoved(adapterPosition)
notifyItemRangeChanged(adapterPosition, itemCount)
} else {
Toast.makeText(app, R.string.can_delete_source, Toast.LENGTH_SHORT).show()
Toast.makeText(
app,
R.string.can_delete_source,
Toast.LENGTH_SHORT
).show()
}
}
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
Toast.makeText(app, R.string.can_delete_source, Toast.LENGTH_SHORT).show()
Toast.makeText(
app,
R.string.can_delete_source,
Toast.LENGTH_SHORT
).show()
}
})
}
}
}
}

View File

@ -7,27 +7,29 @@ import retrofit2.Call
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
class MercuryApi(private val key: String) {
class MercuryApi(private val key: String, shouldLog: Boolean) {
private val service: MercuryService
init {
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 gson = GsonBuilder()
.setLenient()
.create()
val retrofit =
Retrofit
.Builder()
.baseUrl("https://mercury.postlight.com")
.client(client)
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
Retrofit
.Builder()
.baseUrl("https://mercury.postlight.com")
.client(client)
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
service = retrofit.create(MercuryService::class.java)
}

View File

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

View File

@ -1,13 +1,10 @@
package apps.amine.bou.readerforselfoss.api.mercury
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.Query
interface MercuryService {
@GET("parser")
fun parseUrl(@Query("url") url: String, @Header("x-api-key") key: String): Call<ParsedContent>

View File

@ -1,21 +1,22 @@
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.JsonElement
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> {
@Throws(JsonParseException::class)
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Boolean? =
try {
json.asInt == 1
} catch (e: Exception) {
json.asBoolean
}
override fun deserialize(
json: JsonElement,
typeOfT: Type,
context: JsonDeserializationContext
): Boolean? =
try {
json.asInt == 1
} catch (e: Exception) {
json.asBoolean
}
}

View File

@ -2,13 +2,8 @@ package apps.amine.bou.readerforselfoss.api.selfoss
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.support.v7.app.AlertDialog
import android.widget.Toast
import apps.amine.bou.readerforselfoss.LoginActivity
import apps.amine.bou.readerforselfoss.R
import java.util.concurrent.ConcurrentHashMap
import apps.amine.bou.readerforselfoss.utils.Config
import apps.amine.bou.readerforselfoss.utils.getUnsafeHttpClient
import com.burgstaller.okhttp.AuthenticationCacheInterceptor
import com.burgstaller.okhttp.CachingAuthenticatorDecorator
import com.burgstaller.okhttp.DispatchingAuthenticator
@ -18,62 +13,82 @@ import com.burgstaller.okhttp.digest.Credentials
import com.burgstaller.okhttp.digest.DigestAuthenticator
import com.google.gson.GsonBuilder
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Call
import retrofit2.Retrofit
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) {
class SelfossApi(
c: Context,
callingActivity: Activity,
isWithSelfSignedCert: Boolean,
shouldLog: Boolean
) {
private lateinit var service: SelfossService
private val config: Config = Config(c)
private val userName: String
private val password: String
fun Credentials.createAuthenticator(): DispatchingAuthenticator =
DispatchingAuthenticator.Builder()
.with("digest", DigestAuthenticator(this))
.with("basic", BasicAuthenticator(this))
.build()
fun OkHttpClient.Builder.maybeWithSelfSigned(isWithSelfSignedCert: Boolean): OkHttpClient.Builder =
if (isWithSelfSignedCert) {
getUnsafeHttpClient()
} else {
this
}
fun DispatchingAuthenticator.getHttpClien(): OkHttpClient {
fun Credentials.createAuthenticator(): DispatchingAuthenticator =
DispatchingAuthenticator.Builder()
.with("digest", DigestAuthenticator(this))
.with("basic", BasicAuthenticator(this))
.build()
fun DispatchingAuthenticator.getHttpClien(isWithSelfSignedCert: Boolean): OkHttpClient.Builder {
val authCache = ConcurrentHashMap<String, CachingAuthenticator>()
return OkHttpClient
.Builder()
.authenticator(CachingAuthenticatorDecorator(this, authCache))
.addInterceptor(AuthenticationCacheInterceptor(authCache))
.build()
.Builder()
.maybeWithSelfSigned(isWithSelfSignedCert)
.authenticator(CachingAuthenticatorDecorator(this, authCache))
.addInterceptor(AuthenticationCacheInterceptor(authCache))
}
init {
userName = config.userLogin
password = config.userPassword
val authenticator =
Credentials(
config.httpUserLogin,
config.httpUserPassword
).createAuthenticator()
Credentials(
config.httpUserLogin,
config.httpUserPassword
).createAuthenticator()
val gson =
GsonBuilder()
.registerTypeAdapter(Boolean::class.javaPrimitiveType, BooleanTypeAdapter())
.setLenient()
.create()
GsonBuilder()
.registerTypeAdapter(Boolean::class.javaPrimitiveType, BooleanTypeAdapter())
.setLenient()
.create()
val logging = HttpLoggingInterceptor()
logging.level = if (shouldLog) {
HttpLoggingInterceptor.Level.BODY
} else {
HttpLoggingInterceptor.Level.NONE
}
val httpClient = authenticator.getHttpClien(isWithSelfSignedCert)
httpClient.addInterceptor(logging)
try {
val retrofit =
Retrofit
.Builder()
.baseUrl(config.baseUrl)
.client(authenticator.getHttpClien())
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
Retrofit
.Builder()
.baseUrl(config.baseUrl)
.client(httpClient.build())
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
service = retrofit.create(SelfossService::class.java)
} catch (e: IllegalArgumentException) {
Config.logoutAndRedirect(c, callingActivity, config.settings.edit(), baseUrlFail = true)
@ -81,34 +96,59 @@ class SelfossApi(c: Context, callingActivity: Activity) {
}
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>> =
getItems("read", tag, sourceId, search)
fun readItems(
tag: String?,
sourceId: Long?,
search: String?,
itemsNumber: Int,
offset: Int
): Call<List<Item>> =
getItems("read", tag, sourceId, search, itemsNumber, offset)
fun newItems(tag: String?, sourceId: Long?, search: String?): Call<List<Item>> =
getItems("unread", tag, sourceId, search)
fun newItems(
tag: String?,
sourceId: Long?,
search: String?,
itemsNumber: Int,
offset: Int
): Call<List<Item>> =
getItems("unread", tag, sourceId, search, itemsNumber, offset)
fun starredItems(tag: String?, sourceId: Long?, search: String?): Call<List<Item>> =
getItems("starred", tag, sourceId, search)
fun starredItems(
tag: String?,
sourceId: Long?,
search: String?,
itemsNumber: Int,
offset: Int
): Call<List<Item>> =
getItems("starred", tag, sourceId, search, itemsNumber, offset)
private fun getItems(type: String, tag: String?, sourceId: Long?, search: String?): Call<List<Item>> =
service.getItems(type, tag, sourceId, search, userName, password)
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, items, offset)
fun markItem(itemId: String): Call<SuccessResponse> =
service.markAsRead(itemId, userName, password)
service.markAsRead(itemId, userName, password)
fun unmarkItem(itemId: String): Call<SuccessResponse> =
service.unmarkAsRead(itemId, userName, password)
service.unmarkAsRead(itemId, userName, password)
fun readAll(ids: List<String>): Call<SuccessResponse> =
service.markAllAsRead(ids, userName, password)
service.markAllAsRead(ids, userName, password)
fun starrItem(itemId: String): Call<SuccessResponse> =
service.starr(itemId, userName, password)
service.starr(itemId, userName, password)
fun unstarrItem(itemId: String): Call<SuccessResponse> =
service.unstarr(itemId, userName, password)
service.unstarr(itemId, userName, password)
val stats: Call<Stats>
get() = service.stats(userName, password)
@ -117,20 +157,23 @@ class SelfossApi(c: Context, callingActivity: Activity) {
get() = service.tags(userName, password)
fun update(): Call<String> =
service.update(userName, password)
service.update(userName, password)
val sources: Call<List<Sources>>
get() = service.sources(userName, password)
fun deleteSource(id: String): Call<SuccessResponse> =
service.deleteSource(id, userName, password)
service.deleteSource(id, userName, password)
fun spouts(): Call<Map<String, Spout>> =
service.spouts(userName, password)
fun createSource(title: String, url: String, spout: String, tags: String, filter: String): Call<SuccessResponse> =
service.createSource(title, url, spout, tags, filter, userName, password)
service.spouts(userName, password)
fun createSource(
title: String,
url: String,
spout: String,
tags: String,
filter: String
): Call<SuccessResponse> =
service.createSource(title, url, spout, tags, filter, userName, password)
}
// codebeat:enable[ARITY,TOO_MANY_FUNCTIONS]

View File

@ -7,55 +7,71 @@ import android.os.Parcelable
import apps.amine.bou.readerforselfoss.utils.Config
import apps.amine.bou.readerforselfoss.utils.isEmptyOrNullOrNullString
import com.google.gson.annotations.SerializedName
private fun constructUrl(config: Config?, path: String, file: String): String {
val baseUriBuilder = Uri.parse(config!!.baseUrl).buildUpon()
baseUriBuilder.appendPath(path).appendPath(file)
return if (file.isEmptyOrNullOrNullString()) ""
else baseUriBuilder.toString()
return if (file.isEmptyOrNullOrNullString()) {
""
} else {
baseUriBuilder.toString()
}
}
data class Tag(
@SerializedName("tag") val tag: String,
@SerializedName("color") val color: String,
@SerializedName("unread") val unread: Int
)
data class Tag(val tag: String, val color: String, val unread: Int)
class SuccessResponse(val success: Boolean) {
class SuccessResponse(@SerializedName("success") val success: Boolean) {
val isSuccess: Boolean
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,
val title: String,
val tags: String,
val spout: String,
val error: String,
val icon: String) {
data class Sources(
@SerializedName("id") val id: String,
@SerializedName("title") val title: String,
@SerializedName("tags") val tags: String,
@SerializedName("spout") val spout: String,
@SerializedName("error") val error: String,
@SerializedName("icon") val icon: String
) {
var config: Config? = null
fun getIcon(app: Context): String {
if (config == null) {
config = Config(app)
}
return constructUrl(config,"favicons", icon)
return constructUrl(config, "favicons", icon)
}
}
data class Item(val id: String,
val datetime: String,
val title: String,
val unread: Boolean,
val starred: Boolean,
val thumbnail: String,
val icon: String,
val link: String,
val sourcetitle: String) : Parcelable {
data class Item(
@SerializedName("id") val id: String,
@SerializedName("datetime") val datetime: String,
@SerializedName("title") val title: String,
@SerializedName("content") val content: String,
@SerializedName("unread") val unread: Boolean,
@SerializedName("starred") val starred: Boolean,
@SerializedName("thumbnail") val thumbnail: String,
@SerializedName("icon") val icon: String,
@SerializedName("link") val link: String,
@SerializedName("sourcetitle") val sourcetitle: String
) : Parcelable {
var config: Config? = null
@ -67,15 +83,16 @@ data class Item(val id: String,
}
constructor(source: Parcel) : this(
id = source.readString(),
datetime = source.readString(),
title = source.readString(),
unread = 0.toByte() != source.readByte(),
starred = 0.toByte() != source.readByte(),
thumbnail = source.readString(),
icon = source.readString(),
link = source.readString(),
sourcetitle = source.readString()
id = source.readString(),
datetime = source.readString(),
title = source.readString(),
content = source.readString(),
unread = 0.toByte() != source.readByte(),
starred = 0.toByte() != source.readByte(),
thumbnail = source.readString(),
icon = source.readString(),
link = source.readString(),
sourcetitle = source.readString()
)
override fun describeContents() = 0
@ -84,6 +101,7 @@ data class Item(val id: String,
dest.writeString(id)
dest.writeString(datetime)
dest.writeString(title)
dest.writeString(content)
dest.writeByte((if (unread) 1 else 0))
dest.writeByte((if (starred) 1 else 0))
dest.writeString(thumbnail)
@ -109,21 +127,26 @@ data class Item(val id: String,
// TODO: maybe find a better way to handle these kind of urls
fun getLinkDecoded(): 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=")) {
stringUrl = link.substringAfter("&amp;url=")
link.substringAfter("&amp;url=")
} else {
stringUrl = this.link.replace("&amp;", "&")
this.link.replace("&amp;", "&")
}
} else {
stringUrl = this.link.replace("&amp;", "&")
this.link.replace("&amp;", "&")
}
// handle :443 => https
if (stringUrl.contains(":443")) {
stringUrl = stringUrl.replace(":443", "").replace("http://", "https://")
}
// handle url not starting with http
if (stringUrl.startsWith("//")) {
stringUrl = "http:" + stringUrl
}
return stringUrl
}
}

View File

@ -5,91 +5,114 @@ import retrofit2.http.DELETE
import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded
import retrofit2.http.GET
import retrofit2.http.Headers
import retrofit2.http.POST
import retrofit2.http.Path
import retrofit2.http.Query
// codebeat:disable[ARITY]
internal interface SelfossService {
@GET("login")
fun loginToSelfoss(@Query("username") username: String, @Query("password") password: String): Call<SuccessResponse>
@GET("items")
fun getItems(@Query("type") type: String,
@Query("tag") tag: String?,
@Query("source") source: Long?,
@Query("search") search: String?,
@Query("username") username: String,
@Query("password") password: String): Call<List<Item>>
fun getItems(
@Query("type") type: String,
@Query("tag") tag: String?,
@Query("source") source: Long?,
@Query("search") search: String?,
@Query("username") username: String,
@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}")
fun markAsRead(@Path("id") id: String,
@Query("username") username: String,
@Query("password") password: String): Call<SuccessResponse>
fun markAsRead(
@Path("id") id: String,
@Query("username") username: String,
@Query("password") password: String
): Call<SuccessResponse>
@Headers("Content-Type: application/x-www-form-urlencoded")
@POST("unmark/{id}")
fun unmarkAsRead(@Path("id") id: String,
@Query("username") username: String,
@Query("password") password: String): Call<SuccessResponse>
fun unmarkAsRead(
@Path("id") id: String,
@Query("username") username: String,
@Query("password") password: String
): Call<SuccessResponse>
@FormUrlEncoded
@POST("mark")
fun markAllAsRead(@Field("ids[]") ids: List<String>,
@Query("username") username: String,
@Query("password") password: String): Call<SuccessResponse>
fun markAllAsRead(
@Field("ids[]") ids: List<String>,
@Query("username") username: String,
@Query("password") password: String
): Call<SuccessResponse>
@Headers("Content-Type: application/x-www-form-urlencoded")
@POST("starr/{id}")
fun starr(@Path("id") id: String,
@Query("username") username: String,
@Query("password") password: String): Call<SuccessResponse>
fun starr(
@Path("id") id: String,
@Query("username") username: String,
@Query("password") password: String
): Call<SuccessResponse>
@Headers("Content-Type: application/x-www-form-urlencoded")
@POST("unstarr/{id}")
fun unstarr(@Path("id") id: String,
@Query("username") username: String,
@Query("password") password: String): Call<SuccessResponse>
fun unstarr(
@Path("id") id: String,
@Query("username") username: String,
@Query("password") password: String
): Call<SuccessResponse>
@GET("stats")
fun stats(@Query("username") username: String,
@Query("password") password: String): Call<Stats>
fun stats(
@Query("username") username: String,
@Query("password") password: String
): Call<Stats>
@GET("tags")
fun tags(@Query("username") username: String,
@Query("password") password: String): Call<List<Tag>>
fun tags(
@Query("username") username: String,
@Query("password") password: String
): Call<List<Tag>>
@GET("update")
fun update(@Query("username") username: String,
@Query("password") password: String): Call<String>
fun update(
@Query("username") username: String,
@Query("password") password: String
): Call<String>
@GET("sources/spouts")
fun spouts(@Query("username") username: String,
@Query("password") password: String): Call<Map<String, Spout>>
fun spouts(
@Query("username") username: String,
@Query("password") password: String
): Call<Map<String, Spout>>
@GET("sources/list")
fun sources(@Query("username") username: String,
@Query("password") password: String): Call<List<Sources>>
fun sources(
@Query("username") username: String,
@Query("password") password: String
): Call<List<Sources>>
@DELETE("source/{id}")
fun deleteSource(@Path("id") id: String,
@Query("username") username: String,
@Query("password") password: String): Call<SuccessResponse>
fun deleteSource(
@Path("id") id: String,
@Query("username") username: String,
@Query("password") password: String
): Call<SuccessResponse>
@FormUrlEncoded
@POST("source")
fun createSource(@Field("title") title: String,
@Field("url") url: String,
@Field("spout") spout: String,
@Field("tags") tags: String,
@Field("filter") filter: String,
@Query("username") username: String,
@Query("password") password: String): Call<SuccessResponse>
fun createSource(
@Field("title") title: String,
@Field("url") url: String,
@Field("spout") spout: String,
@Field("tags") tags: String,
@Field("filter") filter: String,
@Query("username") username: String,
@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
* to be used with AppCompat.
*/
public abstract class AppCompatPreferenceActivity extends PreferenceActivity {
public abstract class AppCompatPreferenceActivity extends PreferenceActivity { //NOSONAR
private AppCompatDelegate mDelegate;
@ -116,6 +116,7 @@ public abstract class AppCompatPreferenceActivity extends PreferenceActivity {
getDelegate().onDestroy();
}
@Override
public void invalidateOptionsMenu() {
getDelegate().invalidateOptionsMenu();
}

View File

@ -1,25 +1,35 @@
package apps.amine.bou.readerforselfoss.settings;
import java.util.List;
import android.annotation.TargetApi;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.preference.EditTextPreference;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceActivity;
import android.preference.SwitchPreference;
import android.support.v7.app.ActionBar;
import android.preference.PreferenceFragment;
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.widget.Toast;
import java.util.List;
import apps.amine.bou.readerforselfoss.BuildConfig;
import apps.amine.bou.readerforselfoss.R;
import apps.amine.bou.readerforselfoss.utils.Config;
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
* 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
* to reflect its new value.
@ -116,9 +126,11 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
* This method stops fragment injection in malicious applications.
* Make sure to deny any unknown fragments here.
*/
@Override
protected boolean isValidFragment(String fragmentName) {
return PreferenceFragment.class.getName().equals(fragmentName)
|| GeneralPreferenceFragment.class.getName().equals(fragmentName)
|| DebugPreferenceFragment.class.getName().equals(fragmentName)
|| LinksPreferenceFragment.class.getName().equals(fragmentName);
}
@ -144,6 +156,63 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
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
@ -177,7 +246,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
findPreference( "trackerLink" ).setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
openUrl(Uri.parse(getString(R.string.tracker_url)));
openUrl(Uri.parse(BuildConfig.TRACKER_URL));
return true;
}
});
@ -185,7 +254,15 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
findPreference("sourceLink").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
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;
}
});

View File

@ -5,9 +5,6 @@ import android.content.Context
import android.support.annotation.ColorInt
import android.util.TypedValue
import apps.amine.bou.readerforselfoss.R
import java.lang.reflect.AccessibleObject.setAccessible
class AppColors(a: Activity) {
@ColorInt val accent: Int
@ -22,7 +19,7 @@ class AppColors(a: Activity) {
val method = wrapper!!.getMethod("getThemeResId")
method.isAccessible = true
isDarkTheme = when(method.invoke(a.baseContext)) {
isDarkTheme = when (method.invoke(a.baseContext)) {
R.style.NoBarTealOrangeDark,
R.style.NoBarDark,
R.style.NoBarBlueAmberDark,

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,57 +5,17 @@ import android.content.Intent
import android.content.SharedPreferences
import android.net.Uri
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.api.selfoss.Item
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 =
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
}
import com.google.firebase.remoteconfig.FirebaseRemoteConfig
fun String?.isEmptyOrNullOrNullString(): Boolean =
this == null || this == "null" || this.isEmpty()
this == null || this == "null" || this.isEmpty()
fun Context.checkApkVersion(settings: SharedPreferences,
editor: SharedPreferences.Editor,
mFirebaseRemoteConfig: FirebaseRemoteConfig) = {
fun Context.checkApkVersion(
settings: SharedPreferences,
editor: SharedPreferences.Editor,
mFirebaseRemoteConfig: FirebaseRemoteConfig
) = {
fun isThereAnUpdate() {
val APK_LINK = "github_apk"
@ -65,31 +25,35 @@ fun Context.checkApkVersion(settings: SharedPreferences,
val alertDialog = AlertDialog.Builder(this).create()
alertDialog.setTitle(getString(R.string.new_apk_available_title))
alertDialog.setMessage(getString(R.string.new_apk_available_message))
alertDialog.setButton(AlertDialog.BUTTON_POSITIVE, getString(R.string.new_apk_available_get)) { _, _ ->
alertDialog.setButton(
AlertDialog.BUTTON_POSITIVE,
getString(R.string.new_apk_available_get)
) { _, _ ->
editor.putString(APK_LINK, apkLink)
editor.apply()
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(apkLink))
startActivity(browserIntent)
}
alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, getString(R.string.new_apk_available_no),
{ dialog, _ ->
editor.putString(APK_LINK, apkLink)
editor.apply()
dialog.dismiss()
})
alertDialog.setButton(
AlertDialog.BUTTON_NEUTRAL, getString(R.string.new_apk_available_no),
{ dialog, _ ->
editor.putString(APK_LINK, apkLink)
editor.apply()
dialog.dismiss()
}
)
alertDialog.show()
}
}
mFirebaseRemoteConfig.fetch(43200)
.addOnCompleteListener { task ->
if (task.isSuccessful) {
mFirebaseRemoteConfig.activateFetched()
}
.addOnCompleteListener { task ->
if (task.isSuccessful) {
mFirebaseRemoteConfig.activateFetched()
}
isThereAnUpdate()
}
isThereAnUpdate()
}
}
fun String.longHash(): Long {
@ -97,17 +61,18 @@ fun String.longHash(): Long {
val l = this.length
val chars = this.toCharArray()
for (i in 0..l - 1) {
for (i in 0 until l) {
h = 31 * h + chars[i].toLong()
}
return h
}
fun String.toStringUriWithHttp() =
if (!this.startsWith("https://") && !this.startsWith("http://"))
"http://" + this
else
this
fun String.toStringUriWithHttp(): String =
if (!this.startsWith("https://") && !this.startsWith("http://")) {
"http://" + this
} else {
this
}
fun Context.shareLink(itemUrl: String) {
val sendIntent = Intent()
@ -115,12 +80,10 @@ fun Context.shareLink(itemUrl: String) {
sendIntent.action = Intent.ACTION_SEND
sendIntent.putExtra(Intent.EXTRA_TEXT, itemUrl.toStringUriWithHttp())
sendIntent.type = "text/plain"
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)
startActivity(
Intent.createChooser(
sendIntent,
getString(R.string.share)
).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
)
}

View File

@ -6,7 +6,6 @@ import android.content.Intent
import android.content.SharedPreferences
import apps.amine.bou.readerforselfoss.LoginActivity
class Config(c: Context) {
val settings: SharedPreferences = c.getSharedPreferences(settingsName, Context.MODE_PRIVATE)
@ -26,21 +25,23 @@ class Config(c: Context) {
val httpUserPassword: String
get() = settings.getString("httpPassword", "")
companion object {
val settingsName = "paramsselfoss"
fun logoutAndRedirect(c: Context,
callingActivity: Activity,
editor: SharedPreferences.Editor,
baseUrlFail: Boolean = false): Boolean {
fun logoutAndRedirect(
c: Context,
callingActivity: Activity,
editor: SharedPreferences.Editor,
baseUrlFail: Boolean = false
): Boolean {
editor.remove("url")
editor.remove("login")
editor.remove("password")
editor.apply()
val intent = Intent(c, LoginActivity::class.java)
if (baseUrlFail)
if (baseUrlFail) {
intent.putExtra("baseUrlFail", baseUrlFail)
}
c.startActivity(intent)
callingActivity.finish()
return true

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

@ -6,7 +6,6 @@ import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.*
fun String.toTextDrawableString(): String {
val textDrawable = StringBuilder()
for (s in this.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) {
@ -16,12 +15,12 @@ fun String.toTextDrawableString(): String {
}
fun Item.sourceAndDateText(): String {
var formattedDate: String = try {
val formattedDate: String = try {
" " + DateUtils.getRelativeTimeSpanString(
SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(this.datetime).time,
Date().time,
DateUtils.MINUTE_IN_MILLIS,
DateUtils.FORMAT_ABBREV_RELATIVE
SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(this.datetime).time,
Date().time,
DateUtils.MINUTE_IN_MILLIS,
DateUtils.FORMAT_ABBREV_RELATIVE
)
} catch (e: ParseException) {
e.printStackTrace()

View File

@ -7,22 +7,24 @@ import android.content.Intent
import android.graphics.BitmapFactory
import android.net.Uri
import android.support.customtabs.CustomTabsIntent
import xyz.klinker.android.drag_dismiss.DragDismissIntentBuilder
import android.util.Patterns
import android.widget.Toast
import apps.amine.bou.readerforselfoss.R
import apps.amine.bou.readerforselfoss.ReaderActivity
import apps.amine.bou.readerforselfoss.api.selfoss.Item
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
import okhttp3.HttpUrl
fun Context.buildCustomTabsIntent(): CustomTabsIntent {
val actionIntent = Intent(Intent.ACTION_SEND)
actionIntent.type = "text/plain"
val createPendingShareIntent: PendingIntent = PendingIntent.getActivity(this, 0, actionIntent, 0)
val createPendingShareIntent: PendingIntent = PendingIntent.getActivity(
this,
0,
actionIntent,
0
)
val intentBuilder = CustomTabsIntent.Builder()
@ -32,53 +34,105 @@ fun Context.buildCustomTabsIntent(): CustomTabsIntent {
intentBuilder.setShowTitle(true)
intentBuilder.setStartAnimations(this,
intentBuilder.setStartAnimations(
this,
R.anim.slide_in_right,
R.anim.slide_out_left)
intentBuilder.setExitAnimations(this,
R.anim.slide_out_left
)
intentBuilder.setExitAnimations(
this,
android.R.anim.slide_in_left,
android.R.anim.slide_out_right)
android.R.anim.slide_out_right
)
val closeicon = BitmapFactory.decodeResource(resources, R.drawable.ic_close_white_24dp)
intentBuilder.setCloseButtonIcon(closeicon)
val shareLabel = this.getString(R.string.label_share)
val icon = BitmapFactory.decodeResource(resources,
R.drawable.ic_share_white_24dp)
val icon = BitmapFactory.decodeResource(
resources,
R.drawable.ic_share_white_24dp
)
intentBuilder.setActionButton(icon, shareLabel, createPendingShareIntent)
return intentBuilder.build()
}
fun Context.openItemUrl(linkDecoded: String,
customTabsIntent: CustomTabsIntent,
internalBrowser: Boolean,
articleViewer: Boolean,
app: Activity) {
if (!internalBrowser || !linkDecoded.isUrlValid()) {
openInBrowser(linkDecoded, app)
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 {
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.
.build(intent)
intent.putExtra("url", linkDecoded)
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)
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,
content: String,
image: String,
title: String,
source: String,
customTabsIntent: CustomTabsIntent,
internalBrowser: Boolean,
articleViewer: Boolean,
app: Activity
) {
if (!linkDecoded.isUrlValid()) {
Toast.makeText(
this,
this.getString(R.string.cant_open_invalid_url),
Toast.LENGTH_LONG
).show()
} else {
if (!internalBrowser) {
openInBrowser(linkDecoded, app)
} else {
this.openItemUrlInternally(
linkDecoded,
content,
image,
title,
source,
customTabsIntent,
articleViewer,
app
)
}
}
}
@ -88,3 +142,24 @@ private fun openInBrowser(linkDecoded: String, app: Activity) {
intent.data = Uri.parse(linkDecoded)
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,53 @@
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

@ -2,7 +2,6 @@ package apps.amine.bou.readerforselfoss.utils.bottombar
import com.ashokvarma.bottomnavigation.TextBadgeItem
fun TextBadgeItem.removeBadge(): TextBadgeItem {
this.setText("")
this.hide()
@ -10,7 +9,4 @@ fun TextBadgeItem.removeBadge(): TextBadgeItem {
}
fun TextBadgeItem.maybeShow(): TextBadgeItem =
if (this.isHidden)
this.show()
else
this
if (this.isHidden) this.show() else this

View File

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

View File

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

View File

@ -8,8 +8,6 @@ import android.widget.TextView
import apps.amine.bou.readerforselfoss.R
open class CustomBaseViewHolder(var view: View) : RecyclerView.ViewHolder(view) {
var icon: ImageView = view.findViewById(R.id.material_drawer_icon)
var name: TextView = view.findViewById(R.id.material_drawer_name)

View File

@ -15,8 +15,6 @@ import com.mikepenz.materialdrawer.util.DrawerImageLoader
import com.mikepenz.materialdrawer.util.DrawerUIUtils
import com.mikepenz.materialize.util.UIUtils
abstract class CustomUrlBasePrimaryDrawerItem<T, VH : RecyclerView.ViewHolder> : BaseDrawerItem<T, VH>() {
fun withIcon(url: String): T {
this.icon = ImageHolder(url)
@ -77,7 +75,10 @@ abstract class CustomUrlBasePrimaryDrawerItem<T, VH : RecyclerView.ViewHolder> :
val selectedIconColor = getSelectedIconColor(ctx)
//set the background for the item
UIUtils.setBackground(viewHolder.view, UIUtils.getSelectableBackground(ctx, selectedColor, true))
UIUtils.setBackground(
viewHolder.view,
UIUtils.getSelectableBackground(ctx, selectedColor, true)
)
//set the text for the name
StringHolder.applyTo(this.getName(), viewHolder.name)
//set the text for the description or hide
@ -86,8 +87,11 @@ abstract class CustomUrlBasePrimaryDrawerItem<T, VH : RecyclerView.ViewHolder> :
//set the colors for textViews
viewHolder.name.setTextColor(getTextColorStateList(color, selectedTextColor))
//set the description text color
ColorHolder.applyToOr(descriptionTextColor,
viewHolder.description, getTextColorStateList(color, selectedTextColor))
ColorHolder.applyToOr(
descriptionTextColor,
viewHolder.description,
getTextColorStateList(color, selectedTextColor)
)
//define the typeface for our textViews
if (getTypeface() != null) {

View File

@ -5,15 +5,11 @@ import android.support.annotation.LayoutRes
import android.support.annotation.StringRes
import android.view.View
import android.widget.TextView
import apps.amine.bou.readerforselfoss.R
import com.mikepenz.materialdrawer.holder.BadgeStyle
import com.mikepenz.materialdrawer.holder.StringHolder
import com.mikepenz.materialdrawer.model.interfaces.ColorfulBadgeable
import apps.amine.bou.readerforselfoss.R
class CustomUrlPrimaryDrawerItem : CustomUrlBasePrimaryDrawerItem<CustomUrlPrimaryDrawerItem, CustomUrlPrimaryDrawerItem.ViewHolder>(), ColorfulBadgeable<CustomUrlPrimaryDrawerItem> {
protected var mBadge: StringHolder = StringHolder("")
protected var mBadgeStyle = BadgeStyle()
@ -67,7 +63,10 @@ class CustomUrlPrimaryDrawerItem : CustomUrlBasePrimaryDrawerItem<CustomUrlPrima
val badgeVisible = StringHolder.applyToOrHide(mBadge, viewHolder.badge)
//style the badge if it is visible
if (badgeVisible) {
mBadgeStyle.style(viewHolder.badge, getTextColorStateList(getColor(ctx), getSelectedTextColor(ctx)))
mBadgeStyle.style(
viewHolder.badge,
getTextColorStateList(getColor(ctx), getSelectedTextColor(ctx))
)
viewHolder.badgeContainer.visibility = View.VISIBLE
} else {
viewHolder.badgeContainer.visibility = View.GONE
@ -89,6 +88,5 @@ class CustomUrlPrimaryDrawerItem : CustomUrlBasePrimaryDrawerItem<CustomUrlPrima
class ViewHolder(view: View) : CustomBaseViewHolder(view) {
val badgeContainer: View = view.findViewById(R.id.material_drawer_badge_container)
val badge: TextView = view.findViewById(R.id.material_drawer_badge)
}
}

View File

@ -0,0 +1,39 @@
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,33 @@
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">
<item
android:drawable="@color/white"/>
android:drawable="@color/ic_launcher_background"/>
<item>
<bitmap
android:gravity="center"
android:src="@mipmap/ic_launcher"/>
android:src="@mipmap/ic_launcher_foreground"/>
</item>
</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:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:theme="@style/ToolBarStyle" />
app:theme="@style/ToolBarStyle"
app:popupTheme="?attr/toolbarPopupTheme" />
</android.support.design.widget.AppBarLayout>

View File

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

View File

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

View File

@ -1,126 +1,148 @@
<?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:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="apps.amine.bou.readerforselfoss.ReaderActivity"
android:background="?android:attr/windowBackground">
android:layout_height="match_parent">
<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/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.support.v4.widget.NestedScrollView
android:id="@+id/nestedScrollView"
android:layout_width="match_parent"
android:layout_height="35dp"
android:layout_marginBottom="0dp"
android:layout_marginEnd="0dp"
android:layout_marginRight="16dp"
android:layout_marginTop="16dp"
android:background="#BBBBBB"
android:layout_height="match_parent">
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<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_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/content"
app:layout_constraintVertical_bias="1.0">
app:layout_constraintLeft_toLeftOf="parent"
android:layout_gravity="end|bottom|right">
<ImageButton
android:id="@+id/browserBtn"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_centerVertical="true"
<com.github.rubensousa.floatingtoolbar.FloatingToolbar
android:id="@+id/floatingToolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
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_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"
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" />
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:src="@drawable/ic_add"
app:backgroundTint="?attr/colorAccent"
app:fabSize="mini"
app:rippleColor="?attr/colorAccentDark" />
</FrameLayout>
<ImageButton
android:id="@+id/shareBtn"
android:layout_width="35dp"
android:layout_height="35dp"
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"
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" />
<FrameLayout
android:id="@+id/progressBar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
android:animateLayoutChanges="true"
android:alpha="0.8"
android:background="@color/black"
android:clickable="false">
</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"?>
<RelativeLayout
<android.support.design.widget.CoordinatorLayout
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"
@ -16,28 +16,25 @@
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:theme="@style/ToolBarStyle" />
app:theme="@style/ToolBarStyle"
app:popupTheme="?attr/toolbarPopupTheme" />
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:id="@+id/activity_sources"
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
</android.support.v7.widget.RecyclerView>
<com.melnykov.fab.FloatingActionButton
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom|right"
android:src="@drawable/ic_add_black_24dp"
android:tint="?android:textColorPrimary"
fab:fab_colorNormal="?attr/colorAccent"
fab:fab_colorPressed="?attr/colorAccentDark"
fab:fab_colorRipple="@color/pink"
android:src="@drawable/ic_add"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:layout_alignParentBottom="true"
@ -45,5 +42,6 @@
android:layout_alignParentEnd="true"
android:layout_marginBottom="16dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"/>
</RelativeLayout>
android:layout_marginRight="16dp"
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"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="8dp"
app:layout_constraintHorizontal_bias="0.62"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
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:id="@+id/card"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="0dp"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintHorizontal_bias="0.62"
app:layout_constraintTop_toTopOf="parent"
card_view:cardElevation="2dp"
card_view:cardUseCompatPadding="true"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp">
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/itemImage"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:cropToPadding="true"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/background_splash"
card_view:layout_constraintBottom_toTopOf="@+id/constraintLayout" />
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:id="@+id/constraintLayout"
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
android:layout_width="0dp"
android:layout_height="wrap_content"
app:srcCompat="@drawable/background_splash"
android:id="@+id/itemImage"
android:scaleType="fitCenter"
android:adjustViewBounds="true"
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"
android:id="@+id/sourceImage"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_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_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/sourceTitleAndDate">
<ImageView
android:layout_height="40dp"
app:srcCompat="@drawable/background_splash"
android:id="@+id/sourceImage"
app:layout_constraintLeft_toLeftOf="parent"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="8dp"
android:layout_width="40dp" />
<com.like.LikeButton
android:id="@+id/favButton"
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
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" />
android:elevation="5dp"
android:padding="4dp"
app:icon_size="22dp"
app:icon_type="heart" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/sourceTitleAndDate"
android:textSize="14sp"
tools:text="Google Actualité Il y a 5h"
android:textAlignment="viewStart"
app:layout_constraintTop_toBottomOf="@+id/title"
android:layout_marginTop="8dp"
app:layout_constraintLeft_toLeftOf="@+id/title"
android:gravity="start" />
<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" />
<RelativeLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/sourceTitleAndDate"
android:layout_marginTop="16dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent">
<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" />
<com.like.LikeButton
app:icon_type="heart"
app:icon_size="22dp"
android:id="@+id/favButton"
android:layout_width="35dp"
android:layout_height="35dp"
</RelativeLayout>
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.v7.widget.CardView>
</android.support.constraint.ConstraintLayout>
</android.support.v7.widget.CardView>

View File

@ -1,55 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="88dp">
<android.support.constraint.Guideline
android:id="@+id/guideline4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_begin="99dp" />
<ImageView
android:id="@+id/itemImage"
android:layout_width="88dp"
android:layout_height="88dp"
android:src="@color/about_libraries_accent"
app:layout_constraintBottom_toTopOf="@+id/actionBar"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
app:layout_constraintBottom_toTopOf="@+id/guideline4"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginEnd="16dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:fontFamily="sans-serif"
android:gravity="start"
android:maxLines="3"
android:ellipsize="end"
android:textAlignment="viewStart"
android:textAllCaps="false"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/sourceTitleAndDate"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toRightOf="@+id/itemImage"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintStart_toEndOf="@+id/itemImage"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
tools:text="Titre" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/sourceTitleAndDate"
android:textSize="14sp"
tools:text="Google Actualité Il y a 5h"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
android:gravity="start"
android:textAlignment="viewStart"
app:layout_constraintTop_toBottomOf="@+id/title"
android:layout_marginTop="8dp"
app:layout_constraintLeft_toLeftOf="@+id/title"
android:gravity="start" />
android:textSize="14sp"
app:layout_constraintBottom_toTopOf="@+id/guideline4"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@+id/itemImage"
tools:text="Google Actualité Il y a 5h" />
<RelativeLayout
android:id="@+id/actionBar"
@ -58,9 +69,11 @@
android:background="#BBBBBB"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/sourceTitleAndDate">
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/guideline4"
app:layout_constraintVertical_bias="1.0"
tools:visibility="visible">
<com.like.LikeButton
android:id="@+id/favButton"
@ -68,7 +81,6 @@
android:layout_height="35dp"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
@ -110,7 +122,7 @@
android:padding="4dp"
android:scaleType="centerCrop"
android:src="@drawable/ic_open_in_browser_black_24dp" />
</RelativeLayout>
</android.support.constraint.ConstraintLayout>

View File

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

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