Compare commits
348
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
332e9f5108 | ||
|
|
0b91087c07 | ||
|
|
ebbb1ba0f8 | ||
|
|
e9143ae852 | ||
|
|
42e8ecee78 | ||
|
|
4efd76fcbc | ||
|
|
fb1614070e | ||
|
|
c473dd7227 | ||
|
|
76bddb195d | ||
|
|
1e02ad2041 | ||
|
|
f6ab909f8b | ||
|
|
7e520e9bed | ||
|
|
32e2d05014 | ||
|
|
40d9c97f73 | ||
|
|
1aa68d3449 | ||
|
|
aeeac8cccd | ||
|
|
7292edf997 | ||
|
|
f49256c72f | ||
|
|
d02b28b81f | ||
|
|
08117043dd | ||
|
|
63496c993e | ||
|
|
00ef542e49 | ||
|
|
a78c6e6b33 | ||
|
|
363eaf9bf9 | ||
|
|
fec6683701 | ||
|
|
1549edb647 | ||
|
|
3de48ba162 | ||
|
|
a2a3d6f1a7 | ||
|
|
ccab2c7648 | ||
|
|
880dd1db5c | ||
|
|
ed18fea356 | ||
|
|
9816b20bf6 | ||
|
|
0bb2195bff | ||
|
|
ab2d0c4036 | ||
|
|
99fc417109 | ||
|
|
dc304ef8c1 | ||
|
|
c5511880bc | ||
|
|
5fe76d735e | ||
|
|
3064b3b835 | ||
|
|
70dc8af3ce | ||
|
|
53c8c241da | ||
|
|
bdc4f5680b | ||
|
|
ed290573b2 | ||
|
|
1616a97a8a | ||
|
|
d090183007 | ||
|
|
de337fd260 | ||
|
|
12dc206323 | ||
|
|
d47c508dee | ||
|
|
ed75f55437 | ||
|
|
5ad3ad4a57 | ||
|
|
aeac1bd1d4 | ||
|
|
4d18085072 | ||
|
|
0c9f8214ca | ||
|
|
a7ce7ce02e | ||
|
|
820986c7f0 | ||
|
|
8079cae745 | ||
|
|
6f067bd258 | ||
|
|
b6ade0f212 | ||
|
|
27dadc1be3 | ||
|
|
95e4162b4c | ||
|
|
f75557585e | ||
|
|
1b4c26919b | ||
|
|
ad085bf129 | ||
|
|
8fcd551105 | ||
|
|
a0954700e2 | ||
|
|
9705560442 | ||
|
|
1f47a13ce5 | ||
|
|
6f0ff2c975 | ||
|
|
76e5477986 | ||
|
|
7f308d5be3 | ||
|
|
54a43c83e8 | ||
|
|
8fe7266c84 | ||
|
|
d7a46b27b7 | ||
|
|
2257d09fdd | ||
|
|
047c5481c4 | ||
|
|
8a6719f934 | ||
|
|
51a692f3be | ||
|
|
b333f93171 | ||
|
|
89d34a1a71 | ||
|
|
8788e920ce | ||
|
|
d306fb53d3 | ||
|
|
374537b5c7 | ||
|
|
598149d4cd | ||
|
|
50bcf18096 | ||
|
|
a089ced03f | ||
|
|
1f18dddf8b | ||
|
|
f5934e240e | ||
|
|
6b8da2eacf | ||
|
|
f4757a67b7 | ||
|
|
6edeb9d840 | ||
|
|
43ce0fd7bc | ||
|
|
5599f5a8fc | ||
|
|
6fd45ceb4f | ||
|
|
05ad8aac29 | ||
|
|
fa4f2476b7 | ||
|
|
00818a94e9 | ||
|
|
5d5250e44a | ||
|
|
3052b33132 | ||
|
|
50de6f8b5b | ||
|
|
f88a2f415f | ||
|
|
96f9813e01 | ||
|
|
fee739cb17 | ||
|
|
b1814c63b9 | ||
|
|
c1d45678f8 | ||
|
|
3d34e59a94 | ||
|
|
f1133bea8b | ||
|
|
ec64c88ff1 | ||
|
|
be66dbba6c | ||
|
|
8926cdbbf5 | ||
|
|
a956870dec | ||
|
|
8ed7951c9b | ||
|
|
5569a47674 | ||
|
|
0dc6981913 | ||
|
|
4984f2f7ad | ||
|
|
3b6891c84a | ||
|
|
4901e7174c | ||
|
|
8d70e68fe2 | ||
|
|
d3e1527b70 | ||
|
|
0c201301f2 | ||
|
|
6090590f24 | ||
|
|
06b88c783d | ||
|
|
bb75ebf635 | ||
|
|
7d7d0014be | ||
|
|
b3f8d44794 | ||
|
|
29d1e38340 | ||
|
|
2be872e61e | ||
|
|
377c5518f7 | ||
|
|
21be7357b5 | ||
|
|
d47ba2c820 | ||
|
|
a64b14614a | ||
|
|
6a88192e77 | ||
|
|
aa7c630818 | ||
|
|
7fb54f14c7 | ||
|
|
3d709c02b7 | ||
|
|
339d384561 | ||
|
|
50338d51af | ||
|
|
92dbabf899 | ||
|
|
0043021390 | ||
|
|
70ba9b20da | ||
|
|
7fda0a04a1 | ||
|
|
3db3157dc9 | ||
|
|
2089fe60ca | ||
|
|
9606d36670 | ||
|
|
869cf64c54 | ||
|
|
f57ec1f6c0 | ||
|
|
361eea9a06 | ||
|
|
838b4056ac | ||
|
|
0c0a98510b | ||
|
|
be642ed06f | ||
|
|
fd77f38e95 | ||
|
|
c9baab7267 | ||
|
|
86985cfd5b | ||
|
|
1327a4e069 | ||
|
|
c46acbc579 | ||
|
|
4c6a403fae | ||
|
|
78920022bd | ||
|
|
7b16c41e82 | ||
|
|
3389f8bd09 | ||
|
|
8dc25c527d | ||
|
|
46d6bd57c1 | ||
|
|
db014fe13d | ||
|
|
6c293f4cac | ||
|
|
91e5d3736f | ||
|
|
e11dee220f | ||
|
|
fcebf916d2 | ||
|
|
73cc1a7297 | ||
|
|
798f112498 | ||
|
|
38b5e7dc65 | ||
|
|
2799a48f2b | ||
|
|
ad5edae6cd | ||
|
|
9cb02f0272 | ||
|
|
6d24fd9336 | ||
|
|
a3a7b78c96 | ||
|
|
e995286068 | ||
|
|
65fb6d9b7e | ||
|
|
eb02d1efad | ||
|
|
f8d3e1eefb | ||
|
|
218b8fa843 | ||
|
|
9f94af6239 | ||
|
|
d3584ac40e | ||
|
|
90bdb289d0 | ||
|
|
78a08750a2 | ||
|
|
baba851e97 | ||
|
|
2a03783623 | ||
|
|
9f2a4438b1 | ||
|
|
5ee5287ffa | ||
|
|
29547c2c94 | ||
|
|
4846c870fa | ||
|
|
c17980a032 | ||
|
|
a929e419d9 | ||
|
|
487d484bae | ||
|
|
0ca4c04c61 | ||
|
|
c857cf2d67 | ||
|
|
acb502028b | ||
|
|
533636f3a1 | ||
|
|
eb5672901b | ||
|
|
53a8716b51 | ||
|
|
3aaff612af | ||
|
|
fdcd8c6c6a | ||
|
|
bafd478604 | ||
|
|
987513a88b | ||
|
|
a450ab2a3b | ||
|
|
db89fe5aad | ||
|
|
67a30b92f6 | ||
|
|
c397de8c3e | ||
|
|
b4db532c45 | ||
|
|
ebecc9c80a | ||
|
|
4f8556fca8 | ||
|
|
68892fb41b | ||
|
|
6d6f6c72ac | ||
|
|
df5556b945 | ||
|
|
d6c74049c3 | ||
|
|
18946464a2 | ||
|
|
edb5eabee7 | ||
|
|
99a305f3e2 | ||
|
|
68dc5a6acf | ||
|
|
6816461502 | ||
|
|
15b93bbd9e | ||
|
|
cd61e140f6 | ||
|
|
4d861a84e6 | ||
|
|
f24de68618 | ||
|
|
3bcffff444 | ||
|
|
75e9031fa5 | ||
|
|
3b77e24399 | ||
|
|
0a738e895f | ||
|
|
242e5ba035 | ||
|
|
c94612106c | ||
|
|
320924b4ed | ||
|
|
403ecc4521 | ||
|
|
6a50b37364 | ||
|
|
d9d341ac5d | ||
|
|
e9805b731e | ||
|
|
c6d4337cd1 | ||
|
|
173f4b2ff7 | ||
|
|
3b9436264c | ||
|
|
35fe87d79d | ||
|
|
f1bb7ba9ad | ||
|
|
279f229166 | ||
|
|
be1794e27b | ||
|
|
4d4a2039c8 | ||
|
|
3013ae4f35 | ||
|
|
bb3f7d3786 | ||
|
|
f7cc305e44 | ||
|
|
da17f89148 | ||
|
|
ec71ab3c6f | ||
|
|
0d007f1492 | ||
|
|
96f8663b8f | ||
|
|
1a4bc1b301 | ||
|
|
b51ae58a97 | ||
|
|
b126fc32da | ||
|
|
b8d234c415 | ||
|
|
2c8902d404 | ||
|
|
80ad65b196 | ||
|
|
744d9ba72b | ||
|
|
0c1d708588 | ||
|
|
95e79e7c5d | ||
|
|
3ce3260d20 | ||
|
|
641f4f34d3 | ||
|
|
99620cb1c5 | ||
|
|
8f5f33f5d2 | ||
|
|
78e9230b82 | ||
|
|
78aa44c007 | ||
|
|
53fd944f00 | ||
|
|
9e6cb4ee3d | ||
|
|
87ad6f2826 | ||
|
|
9050f5a56f | ||
|
|
3437004082 | ||
|
|
dcf620af87 | ||
|
|
128085a02e | ||
|
|
302040ec25 | ||
|
|
e177c22032 | ||
|
|
a11007113a | ||
|
|
5e7897bcf4 | ||
|
|
9559af3637 | ||
|
|
4c499abcdb | ||
|
|
0055a503b3 | ||
|
|
3a189ee4b6 | ||
|
|
e25dc49271 | ||
|
|
4208a80db8 | ||
|
|
ddb75e0d93 | ||
|
|
8b37e992a2 | ||
|
|
bac59036cd | ||
|
|
6c89a3b77c | ||
|
|
dc2ef39fc6 | ||
|
|
a4806da2c5 | ||
|
|
ee30edb214 | ||
|
|
e4ed663fb3 | ||
|
|
01629309b0 | ||
|
|
059c2991fb | ||
|
|
686ec5dd90 | ||
|
|
eab9df8ed9 | ||
|
|
0107c3d7e2 | ||
|
|
2def2f2e2c | ||
|
|
44c79892a0 | ||
|
|
bc96b314c2 | ||
|
|
8dcf749b4e | ||
|
|
6a56ec6442 | ||
|
|
30e46d7eae | ||
|
|
9458b1834b | ||
|
|
297f797b97 | ||
|
|
c70e80758c | ||
|
|
3bf1d7c4f9 | ||
|
|
173247041a | ||
|
|
3a28772096 | ||
|
|
bd08b8aba3 | ||
|
|
2ceb0f988b | ||
|
|
4ef3b155b8 | ||
|
|
350e24cded | ||
|
|
1bf8a578bc | ||
|
|
4818a101cc | ||
|
|
baebf938ef | ||
|
|
fea57c7b1e | ||
|
|
113dfa68be | ||
|
|
60c6514fa1 | ||
|
|
114485afc3 | ||
|
|
d6a51381b9 | ||
|
|
620f13fd7c | ||
|
|
6577b2c3d7 | ||
|
|
caef522c8b | ||
|
|
40ea07de2e | ||
|
|
7905e4aa12 | ||
|
|
64f4fd708a | ||
|
|
b46e4a018f | ||
|
|
3e999a9be2 | ||
|
|
f656d621e6 | ||
|
|
951cc1e6bd | ||
|
|
d8d4264f1b | ||
|
|
014eeec2b9 | ||
|
|
83837bddc3 | ||
|
|
f97666db92 | ||
|
|
ee08ea41a1 | ||
|
|
4ca64610cb | ||
|
|
4980145e46 | ||
|
|
10cbc19a0c | ||
|
|
15fba2b29b | ||
|
|
096952f88c | ||
|
|
0ea70c1922 | ||
|
|
69ac2e2b44 | ||
|
|
68098f4d84 | ||
|
|
080d52893e | ||
|
|
b02334a8d4 | ||
|
|
27118add22 | ||
|
|
2a6f98a1e8 | ||
|
|
1f67f2fdee | ||
|
|
ebf4d294a8 | ||
|
|
4a4dbacc95 | ||
|
|
687839b5f8 | ||
|
|
8fb339034f |
+3
-14
@@ -8,7 +8,7 @@ Please read the guidelines before contributing, and follow them (or try to) when
|
||||
|
||||
### What you can do to help.
|
||||
|
||||
There are many ways to contribute to this project, you could report bugs, request missing features, suggest enhancements and changes to existing ones. You also can improve the README with useful tips that could help the other users.
|
||||
There are many ways to contribute to this project, you could [translate the app](https://crowdin.com/project/readerforselfoss), report bugs, request missing features, suggest enhancements and changes to existing ones. You also can improve the README with useful tips that could help the other users.
|
||||
|
||||
You can fork the repository, and [help me solve some issues](https://github.com/aminecmi/ReaderforSelfoss/issues?q=is%3Aissue+is%3Aopen+label%3A%22Up+For+Grabs%22) or [develop new things](https://github.com/aminecmi/ReaderforSelfoss/issues)
|
||||
|
||||
@@ -28,6 +28,7 @@ Always check if the web version of your instance is working.
|
||||
|
||||
### Pull requests
|
||||
|
||||
* Don't create a PR for translations. See [here](https://github.com/aminecmi/ReaderforSelfoss/pull/170#issuecomment-355715654) for an explanation why.
|
||||
* 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.
|
||||
@@ -46,15 +47,8 @@ You can directly import this project into IntellIJ/Android Studio.
|
||||
|
||||
You'll have to:
|
||||
|
||||
- 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)
|
||||
|
||||
- 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.**
|
||||
|
||||
### Examples:
|
||||
@@ -64,15 +58,10 @@ You'll have to:
|
||||
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"
|
||||
./gradlew .... -P appLoginUrl="URL" -P appLoginUsername="LOGIN" -P appLoginPassword="PASS"
|
||||
```
|
||||
@@ -5,6 +5,7 @@
|
||||
- [ ] I have updated the documentation accordingly.
|
||||
- [ ] I have added tests to cover my changes.
|
||||
- [ ] All new and existing tests passed.
|
||||
- [ ] This is **NOT** translation related. (See [here](https://github.com/aminecmi/ReaderforSelfoss/pull/170#issuecomment-355715654))
|
||||
|
||||
This closes issue #XXX
|
||||
|
||||
|
||||
+134
@@ -1,3 +1,137 @@
|
||||
**1.7.x**
|
||||
|
||||
- Closing #220.
|
||||
|
||||
- Start of #238. "Add a quick shortcut to open the app on offline mode ?"
|
||||
|
||||
- Closes #216. Issue with selfoss version 2.19.
|
||||
|
||||
- Closes #179. Sync of read/unread/star/unstar items on background task or on app reload with network available.
|
||||
|
||||
- Closes #33. Background sync with settings.
|
||||
|
||||
- Closing #1. Initial article caching.
|
||||
|
||||
- Closing #228 by removing the list action bar. Action buttons are exclusively on the card view from now on.
|
||||
|
||||
- Closing #38. Only doing api calls on network available.
|
||||
|
||||
**1.6.x**
|
||||
|
||||
- Handling hidden tags.
|
||||
|
||||
- Fixed pre-lolipop issue with automatic theme changes.
|
||||
|
||||
- Removed all Build config things.
|
||||
|
||||
- Removed firebase and fabric.
|
||||
|
||||
- Added Acra for optional crash reporting and error logging.
|
||||
|
||||
- Dynamic themes !
|
||||
|
||||
- Strings cleaning.
|
||||
|
||||
- Versions updates.
|
||||
|
||||
- Fixes #215, #208.
|
||||
|
||||
**1.5.7.x**
|
||||
|
||||
- Added confirmation to the mark as read and update menues.
|
||||
|
||||
- Add to favorites from article viewer.
|
||||
|
||||
- Added an option to use a webview in the article viewer (see #149)
|
||||
|
||||
- Fixes (#151 #152 #155 #157 #160 #174) and more.
|
||||
|
||||
- New year fixes !!!
|
||||
|
||||
- Changed page indicator position as it was overlaping content.
|
||||
|
||||
- Now using slack instead of gitter.
|
||||
|
||||
- Moved completely to a webview to fix #161.
|
||||
|
||||
- Fixed typos in French ( Thanks @aancel )
|
||||
|
||||
- Updated the Contribution guide about translations.
|
||||
|
||||
- Better handling for articles update. (See #169)
|
||||
|
||||
- Ability to change the article viewer content font size (see #153)
|
||||
|
||||
- Versions updates * 2.
|
||||
|
||||
- Added padding to the recyclerview.
|
||||
|
||||
**1.5.5.x (didn't last long) AND 1.5.6.x**
|
||||
|
||||
- Toolbar in reader activity.
|
||||
|
||||
- Marking items as read on scroll (with settings to enable/disable).
|
||||
|
||||
- Swapped the title and subtitle in the article viewer.
|
||||
|
||||
- Added an animation to the viewpager.
|
||||
|
||||
- Completed Dutch, Indonesian and Portuguese translations !
|
||||
|
||||
- Fixed #142, #144, #147.
|
||||
|
||||
- Changed versions handling.
|
||||
|
||||
- Removed indonesian english as it was causing issues with the english version of the app.
|
||||
|
||||
**1.5.4.22**
|
||||
|
||||
- You can now scroll through the loaded articles !
|
||||
|
||||
**1.5.4.21**
|
||||
|
||||
- Spanish translation and some Indonesian !
|
||||
|
||||
**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)
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
# ReaderForSelfoss
|
||||
|
||||
[](https://crowdin.com/project/readerforselfoss) [](https://gitter.im/amine-bou/ReaderForSelfoss)
|
||||
|
||||
[](http://jenkins.amine-bou.fr/job/ReaderForSelfoss/)
|
||||
|
||||
[](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).
|
||||
[](https://join.slack.com/t/readerforselfoss/shared_invite/enQtMjkyNzc3NjM2Mjc1LTUzZTZhOGM5YjQ1MTI5MWZiODRjMjE1ZDBmMzQxZmQ3NWZhYTNhMTBjNGEwNmE2ZGFjODU5NjUxZjBkMWJmMDQ) [](https://jenkins.amine-bou.fr/job/ReaderForSelfoss/) [](https://www.codetriage.com/aminecmi/readerforselfoss) [](https://crowdin.com/project/readerforselfoss)
|
||||
|
||||
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).
|
||||
<a href='https://play.google.com/store/apps/details?id=apps.amine.bou.readerforselfoss'><img alt='Get it on Google Play' src='https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png' height="100"/></a> <a href="https://f-droid.org/packages/apps.amine.bou.readerforselfoss"><img src="https://f-droid.org/badge/get-it-on.png" alt="Get it on F-Droid" height="100"></a>
|
||||
|
||||
Also, 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).
|
||||
|
||||
## Join the alpha channel
|
||||
|
||||
**Keep in mind, it could be instable, but you'll have the new updates faster**
|
||||
|
||||
- First, join the google [group](https://groups.google.com/d/forum/reader-for-selfoss-alpha-testing).
|
||||
- Then, join the [alpha channel](https://play.google.com/apps/testing/apps.amine.bou.readerforselfoss) of the app.
|
||||
- You'll be able to update the app for the current alpha version.
|
||||
|
||||
## Want to help ?
|
||||
|
||||
@@ -23,4 +26,4 @@ Check the [Contribution guide](https://github.com/aminecmi/ReaderforSelfoss/blob
|
||||
- [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)
|
||||
- [Ask for help](https://join.slack.com/t/readerforselfoss/shared_invite/enQtMjkyNzc3NjM2Mjc1LTUzZTZhOGM5YjQ1MTI5MWZiODRjMjE1ZDBmMzQxZmQ3NWZhYTNhMTBjNGEwNmE2ZGFjODU5NjUxZjBkMWJmMDQ)
|
||||
+73
-112
@@ -1,51 +1,48 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
maven { url 'https://maven.fabric.io/public' }
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'io.fabric.tools:gradle:1.+'
|
||||
}
|
||||
ext {
|
||||
configuration = [
|
||||
buildDate: new Date()
|
||||
]
|
||||
// This will make me able to build multiple times a day. May break thinks. I may forget it.
|
||||
todaysBuilds = "1"
|
||||
}
|
||||
|
||||
def gitVersion() {
|
||||
def process = "git describe --abbrev=0 --tags".execute()
|
||||
return process.text.substring(1).replaceAll("\\.", "")
|
||||
return process.text.substring(1).replaceAll("\\.", "").trim()
|
||||
}
|
||||
|
||||
def versionCodeFromGit() {
|
||||
println "version code " + gitVersion().toInteger()
|
||||
println "version code " + gitVersion()
|
||||
return gitVersion().toInteger()
|
||||
}
|
||||
|
||||
def versionNameFromGit() {
|
||||
println "version code " + gitVersion().trim()
|
||||
return gitVersion().trim()
|
||||
println "version name " + gitVersion()
|
||||
return gitVersion()
|
||||
}
|
||||
|
||||
apply plugin: 'org.sonarqube'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
|
||||
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'
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 27
|
||||
buildToolsVersion '27.0.0'
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
compileSdkVersion 28
|
||||
buildToolsVersion '28.0.3'
|
||||
defaultConfig {
|
||||
applicationId "apps.amine.bou.readerforselfoss"
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 27
|
||||
targetSdkVersion 28
|
||||
versionCode versionCodeFromGit()
|
||||
versionName versionNameFromGit()
|
||||
|
||||
@@ -58,14 +55,14 @@ android {
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
|
||||
// tests
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
testInstrumentationRunner "androidx.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
|
||||
javaCompileOptions {
|
||||
annotationProcessorOptions {
|
||||
arguments = ["room.schemaLocation":
|
||||
"$projectDir/schemas".toString()]
|
||||
}
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
@@ -85,111 +82,90 @@ android {
|
||||
githubConfig {
|
||||
versionNameSuffix '-github'
|
||||
dimension "build"
|
||||
buildConfigField "boolean", "GITHUB_VERSION", "true"
|
||||
}
|
||||
storeConfig {
|
||||
// As jenkins publishes to alpha first, this is the default suffix now.
|
||||
versionNameSuffix '-store'
|
||||
dimension "build"
|
||||
buildConfigField "boolean", "GITHUB_VERSION", "false"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Testing
|
||||
androidTestCompile 'com.android.support.test.espresso:espresso-core:3.0.1'
|
||||
androidTestCompile 'com.android.support.test:runner:1.0.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0-beta02'
|
||||
androidTestImplementation 'androidx.test:runner:1.1.0-beta02'
|
||||
// Espresso-contrib for DatePicker, RecyclerView, Drawer actions, Accessibility checks, CountingIdlingResource
|
||||
androidTestCompile 'com.android.support.test.espresso:espresso-contrib:3.0.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.1.0-beta02'
|
||||
// Espresso-intents for validation and stubbing of Intents
|
||||
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"
|
||||
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.0-beta02'
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
// Android Support
|
||||
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.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;
|
||||
}
|
||||
implementation "androidx.appcompat:appcompat:$android_version"
|
||||
implementation "com.google.android.material:material:$android_version"
|
||||
implementation "androidx.recyclerview:recyclerview:$android_version"
|
||||
implementation "androidx.legacy:legacy-support-v4:$android_version"
|
||||
implementation "androidx.vectordrawable:vectordrawable:$android_version"
|
||||
implementation "androidx.browser:browser:$android_version"
|
||||
implementation "androidx.cardview:cardview:$android_version"
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha2'
|
||||
|
||||
//multidex
|
||||
compile 'com.android.support:multidex:1.0.2'
|
||||
|
||||
// Intro
|
||||
compile 'agency.tango.android:material-intro-screen:0.0.5'
|
||||
implementation 'androidx.multidex:multidex:2.0.0'
|
||||
|
||||
// About
|
||||
compile('com.mikepenz:aboutlibraries:6.0.0@aar') {
|
||||
implementation('com.mikepenz:aboutlibraries:6.2.0@aar') {
|
||||
transitive = true
|
||||
}
|
||||
|
||||
// Retrofit + http logging + okhttp
|
||||
compile 'com.squareup.retrofit2:retrofit:2.3.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'
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.3.0'
|
||||
implementation 'com.squareup.okhttp3:logging-interceptor:3.9.0'
|
||||
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
|
||||
implementation 'com.burgstaller:okhttp-digest:1.12'
|
||||
|
||||
// Material-ish things
|
||||
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.5'
|
||||
implementation 'com.ashokvarma.android:bottom-navigation-bar:2.0.5'
|
||||
implementation 'com.github.jd-alexander:LikeButton:0.2.3'
|
||||
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
|
||||
|
||||
// glide
|
||||
compile 'com.github.bumptech.glide:glide:4.1.1'
|
||||
compile 'com.github.bumptech.glide:okhttp3-integration:4.1.1'
|
||||
implementation 'com.github.bumptech.glide:glide:4.1.1'
|
||||
implementation 'com.github.bumptech.glide:okhttp3-integration:4.1.1'
|
||||
|
||||
// Asking politely users to rate the app
|
||||
compile 'com.github.stkent:amplify:2.1.0'
|
||||
|
||||
// For the article reader
|
||||
compile 'com.klinkerapps:drag-dismiss-activity:1.5.0'
|
||||
implementation 'com.github.stkent:amplify:2.2.0'
|
||||
|
||||
// Drawer
|
||||
implementation 'co.zsmb:materialdrawer-kt:1.2.1'
|
||||
compile 'com.anupcowkur:reservoir:3.1.0'
|
||||
implementation 'co.zsmb:materialdrawer-kt:2.0.1'
|
||||
|
||||
// Themes
|
||||
compile 'com.52inc:scoops:1.0.0'
|
||||
implementation 'com.52inc:scoops:1.0.0'
|
||||
implementation 'com.jaredrummler:colorpicker:1.0.2'
|
||||
implementation 'com.github.rubensousa:floatingtoolbar:1.5.1'
|
||||
|
||||
// Github issues reporter
|
||||
compile 'com.heinrichreimersoftware:android-issue-reporter:1.3.1'
|
||||
// Pager
|
||||
implementation 'me.relex:circleindicator:2.0.0@aar'
|
||||
|
||||
compile 'com.github.rubensousa:floatingtoolbar:1.5.1'
|
||||
implementation 'androidx.core:core-ktx:1.0.0'
|
||||
|
||||
// Crash
|
||||
implementation 'ch.acra:acra-http:5.2.1'
|
||||
implementation 'ch.acra:acra-dialog:5.2.1'
|
||||
|
||||
implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
|
||||
|
||||
implementation "androidx.room:room-runtime:$room_version"
|
||||
kapt "androidx.room:room-compiler:$room_version"
|
||||
|
||||
implementation "android.arch.work:work-runtime-ktx:$work_version"
|
||||
}
|
||||
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
|
||||
|
||||
afterEvaluate {
|
||||
initFabricPropertiesIfNeeded()
|
||||
initAppLoginPropertiesIfNeeded()
|
||||
initAppForSecretPropertiesIfNeeded()
|
||||
}
|
||||
|
||||
def initFabricPropertiesIfNeeded() {
|
||||
def propertiesFile = file('fabric.properties')
|
||||
if (!propertiesFile.exists()) {
|
||||
def commentMessage = "This is autogenerated fabric property from system environment to prevent key to be committed to source control."
|
||||
ant.propertyfile(file: "fabric.properties", comment: commentMessage) {
|
||||
entry(key: "apiSecret", value: crashlyticsdemoApisecret)
|
||||
entry(key: "apiKey", value: crashlyticsdemoApikey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def initAppLoginPropertiesIfNeeded() {
|
||||
@@ -202,19 +178,4 @@ 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"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+11
-18
@@ -30,25 +30,13 @@
|
||||
<fields>;
|
||||
}
|
||||
|
||||
|
||||
##Retrofit
|
||||
#-keep class com.google.gson.** { *; }
|
||||
#-keep class com.google.inject.** { *; }
|
||||
#-keep class org.apache.http.** { *; }
|
||||
#-keep class org.apache.james.mime4j.** { *; }
|
||||
#-keep class javax.inject.** { *; }
|
||||
#-keep class retrofit.** { *; }
|
||||
#-keepclassmembernames interface * {
|
||||
# @retrofit.http.* <methods>;
|
||||
#}
|
||||
#-keep class retrofit.** { *; }
|
||||
#-keep class apps.amine.bou.readerforselfoss.api.selfoss.model.** { *; }
|
||||
#-keepclassmembernames interface * {
|
||||
# @retrofit.http.* <methods>;
|
||||
#}
|
||||
-dontwarn okio.**
|
||||
-dontwarn retrofit2.Platform$Java8
|
||||
-keepattributes Signature
|
||||
-keep class retrofit.** { *; }
|
||||
-keepclasseswithmembers class * {
|
||||
@retrofit.http.* <methods>;
|
||||
}
|
||||
-keepattributes *Annotation*,Signature
|
||||
-keepattributes Exceptions
|
||||
-dontwarn okio.**
|
||||
-dontwarn javax.annotation.Nullable
|
||||
@@ -69,4 +57,9 @@
|
||||
|
||||
-dontwarn com.anupcowkur.reservoir.**
|
||||
|
||||
-dontwarn javax.annotation.**
|
||||
-dontwarn javax.annotation.**
|
||||
|
||||
-keep class android.support.v7.widget.SearchView { *; }
|
||||
|
||||
# maybe remove later ?
|
||||
-keep class * extends androidx.fragment.app.Fragment
|
||||
@@ -0,0 +1,96 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 1,
|
||||
"identityHash": "08ca537d7ac9d4dd216e8e395d70801a",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "tags",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tag` TEXT NOT NULL, `color` TEXT NOT NULL, `unread` INTEGER NOT NULL, PRIMARY KEY(`tag`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "tag",
|
||||
"columnName": "tag",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "color",
|
||||
"columnName": "color",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "unread",
|
||||
"columnName": "unread",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"tag"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "sources",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `title` TEXT NOT NULL, `tags` TEXT NOT NULL, `spout` TEXT NOT NULL, `error` TEXT NOT NULL, `icon` TEXT NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "title",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "tags",
|
||||
"columnName": "tags",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "spout",
|
||||
"columnName": "spout",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "error",
|
||||
"columnName": "error",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "icon",
|
||||
"columnName": "icon",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"08ca537d7ac9d4dd216e8e395d70801a\")"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 2,
|
||||
"identityHash": "6fa6944b04100d68eab61039876a8804",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "tags",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tag` TEXT NOT NULL, `color` TEXT NOT NULL, `unread` INTEGER NOT NULL, PRIMARY KEY(`tag`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "tag",
|
||||
"columnName": "tag",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "color",
|
||||
"columnName": "color",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "unread",
|
||||
"columnName": "unread",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"tag"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "sources",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `title` TEXT NOT NULL, `tags` TEXT NOT NULL, `spout` TEXT NOT NULL, `error` TEXT NOT NULL, `icon` TEXT NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "title",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "tags",
|
||||
"columnName": "tags",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "spout",
|
||||
"columnName": "spout",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "error",
|
||||
"columnName": "error",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "icon",
|
||||
"columnName": "icon",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "items",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `datetime` TEXT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `thumbnail` TEXT NOT NULL, `icon` TEXT NOT NULL, `link` TEXT NOT NULL, `sourcetitle` TEXT NOT NULL, `tags` TEXT NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "datetime",
|
||||
"columnName": "datetime",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "title",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "content",
|
||||
"columnName": "content",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "unread",
|
||||
"columnName": "unread",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "starred",
|
||||
"columnName": "starred",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumbnail",
|
||||
"columnName": "thumbnail",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "icon",
|
||||
"columnName": "icon",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "link",
|
||||
"columnName": "link",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "sourcetitle",
|
||||
"columnName": "sourcetitle",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "tags",
|
||||
"columnName": "tags",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"6fa6944b04100d68eab61039876a8804\")"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 3,
|
||||
"identityHash": "7ad9c4961992c13b670128485ebb3efc",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "tags",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tag` TEXT NOT NULL, `color` TEXT NOT NULL, `unread` INTEGER NOT NULL, PRIMARY KEY(`tag`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "tag",
|
||||
"columnName": "tag",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "color",
|
||||
"columnName": "color",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "unread",
|
||||
"columnName": "unread",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"tag"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "sources",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `title` TEXT NOT NULL, `tags` TEXT NOT NULL, `spout` TEXT NOT NULL, `error` TEXT NOT NULL, `icon` TEXT NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "title",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "tags",
|
||||
"columnName": "tags",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "spout",
|
||||
"columnName": "spout",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "error",
|
||||
"columnName": "error",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "icon",
|
||||
"columnName": "icon",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "items",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `datetime` TEXT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `thumbnail` TEXT NOT NULL, `icon` TEXT NOT NULL, `link` TEXT NOT NULL, `sourcetitle` TEXT NOT NULL, `tags` TEXT NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "datetime",
|
||||
"columnName": "datetime",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "title",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "content",
|
||||
"columnName": "content",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "unread",
|
||||
"columnName": "unread",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "starred",
|
||||
"columnName": "starred",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumbnail",
|
||||
"columnName": "thumbnail",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "icon",
|
||||
"columnName": "icon",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "link",
|
||||
"columnName": "link",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "sourcetitle",
|
||||
"columnName": "sourcetitle",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "tags",
|
||||
"columnName": "tags",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "actions",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `articleid` TEXT NOT NULL, `read` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `unstarred` INTEGER NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "articleId",
|
||||
"columnName": "articleid",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "read",
|
||||
"columnName": "read",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "unread",
|
||||
"columnName": "unread",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "starred",
|
||||
"columnName": "starred",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "unstarred",
|
||||
"columnName": "unstarred",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"7ad9c4961992c13b670128485ebb3efc\")"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
package apps.amine.bou.readerforselfoss
|
||||
|
||||
|
||||
// TODO: test source adding
|
||||
+36
-50
@@ -2,32 +2,35 @@ package apps.amine.bou.readerforselfoss
|
||||
|
||||
import android.content.Context
|
||||
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.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.rule.ActivityTestRule
|
||||
import android.support.test.runner.AndroidJUnit4
|
||||
import androidx.test.InstrumentationRegistry
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.action.ViewActions.closeSoftKeyboard
|
||||
import androidx.test.espresso.action.ViewActions.pressBack
|
||||
import androidx.test.espresso.action.ViewActions.pressKey
|
||||
import androidx.test.espresso.action.ViewActions.typeText
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.contrib.DrawerActions
|
||||
import androidx.test.espresso.intent.Intents
|
||||
import androidx.test.espresso.intent.Intents.intended
|
||||
import androidx.test.espresso.intent.Intents.times
|
||||
import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isRoot
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import androidx.test.rule.ActivityTestRule
|
||||
import androidx.test.runner.AndroidJUnit4
|
||||
import android.view.KeyEvent
|
||||
|
||||
import com.mikepenz.aboutlibraries.ui.LibsActivity
|
||||
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 com.heinrichreimersoftware.androidissuereporter.IssueReporterLauncher
|
||||
import org.junit.After
|
||||
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class HomeActivityEspressoTest {
|
||||
lateinit var context: Context
|
||||
@@ -40,9 +43,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,49 +63,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())
|
||||
|
||||
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))
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun drawerTesting() {
|
||||
|
||||
rule.launchActivity(Intent())
|
||||
|
||||
onView(withId(R.id.material_drawer_layout)).perform(DrawerActions.open())
|
||||
|
||||
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))
|
||||
|
||||
onView(withId(R.id.material_drawer_layout)).perform(DrawerActions.open())
|
||||
|
||||
onView(withId(R.id.material_drawer_layout)).perform(DrawerActions.open())
|
||||
onView(withText(R.string.drawer_action_clear)).perform(click())
|
||||
|
||||
}
|
||||
|
||||
// TODO: test articles opening and actions for cards and lists
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
package apps.amine.bou.readerforselfoss
|
||||
|
||||
import java.util.*
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.support.test.InstrumentationRegistry.getInstrumentation
|
||||
import android.support.test.espresso.Espresso.onView
|
||||
import android.support.test.espresso.action.ViewActions.click
|
||||
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.rule.ActivityTestRule
|
||||
import android.support.test.runner.AndroidJUnit4
|
||||
|
||||
import org.hamcrest.Matchers.allOf
|
||||
import org.hamcrest.Matchers.equalTo
|
||||
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 IntroActivityEspressoTest {
|
||||
|
||||
@Rule @JvmField
|
||||
val rule = ActivityTestRule(IntroActivity::class.java, true, false)
|
||||
|
||||
@Before
|
||||
fun clearData() {
|
||||
val editor =
|
||||
getInstrumentation().targetContext
|
||||
.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
|
||||
.edit()
|
||||
editor.clear()
|
||||
editor.commit()
|
||||
|
||||
Intents.init()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nextEachTimes() {
|
||||
|
||||
rule.launchActivity(Intent())
|
||||
|
||||
onView(withText(R.string.intro_hello_title)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.button_next)).perform(click())
|
||||
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()))
|
||||
onView(withId(R.id.button_next)).perform(click())
|
||||
|
||||
intended(hasComponent(IntroActivity::class.java.name), times(1))
|
||||
intended(hasComponent(LoginActivity::class.java.name), times(1))
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nextBackRandomTimes() {
|
||||
val max = 5
|
||||
val min = 1
|
||||
|
||||
val random = (Random().nextInt(max + 1 - min)) + min
|
||||
|
||||
rule.launchActivity(Intent())
|
||||
|
||||
onView(withText(R.string.intro_hello_title)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.button_next)).perform(click())
|
||||
|
||||
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()))
|
||||
onView(withId(R.id.button_back)).perform(click())
|
||||
}
|
||||
|
||||
onView(withId(R.id.button_next)).perform(click())
|
||||
onView(withText(R.string.intro_all_set_message)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.button_next)).perform(click())
|
||||
|
||||
intended(hasComponent(IntroActivity::class.java.name), times(1))
|
||||
intended(hasComponent(LoginActivity::class.java.name), times(1))
|
||||
|
||||
}
|
||||
|
||||
@After
|
||||
fun releaseIntents() {
|
||||
Intents.release()
|
||||
}
|
||||
}
|
||||
+43
-30
@@ -2,30 +2,33 @@ package apps.amine.bou.readerforselfoss
|
||||
|
||||
import android.content.Context
|
||||
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.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.rule.ActivityTestRule
|
||||
import android.support.test.runner.AndroidJUnit4
|
||||
|
||||
import androidx.test.InstrumentationRegistry
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.action.ViewActions.closeSoftKeyboard
|
||||
import androidx.test.espresso.action.ViewActions.pressBack
|
||||
import androidx.test.espresso.action.ViewActions.typeText
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.intent.Intents
|
||||
import androidx.test.espresso.intent.Intents.intended
|
||||
import androidx.test.espresso.intent.Intents.times
|
||||
import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
|
||||
import androidx.test.espresso.matcher.ViewMatchers
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isRoot
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import androidx.test.rule.ActivityTestRule
|
||||
import androidx.test.runner.AndroidJUnit4
|
||||
import 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 {
|
||||
|
||||
@@ -69,10 +72,8 @@ class LoginActivityEspressoTest {
|
||||
onView(isRoot()).perform(pressBack())
|
||||
|
||||
intended(hasComponent(LoginActivity::class.java.name))
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun wrongLoginUrl() {
|
||||
rule.launchActivity(Intent())
|
||||
@@ -103,7 +104,10 @@ class LoginActivityEspressoTest {
|
||||
onView(withId(R.id.loginLayout)).check(matches(isHintOrErrorEnabled()))
|
||||
onView(withId(R.id.passwordLayout)).check(matches(isHintOrErrorEnabled()))
|
||||
|
||||
onView(withId(R.id.loginView)).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()))
|
||||
|
||||
@@ -111,9 +115,9 @@ class LoginActivityEspressoTest {
|
||||
|
||||
onView(withId(R.id.passwordLayout)).check(
|
||||
matches(
|
||||
isHintOrErrorEnabled())
|
||||
isHintOrErrorEnabled()
|
||||
)
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -125,16 +129,21 @@ class LoginActivityEspressoTest {
|
||||
|
||||
onView(withId(R.id.withLogin)).perform(click())
|
||||
|
||||
onView(withId(R.id.loginView)).perform(click()).perform(typeText(username), closeSoftKeyboard())
|
||||
onView(withId(R.id.loginView)).perform(click()).perform(
|
||||
typeText(username),
|
||||
closeSoftKeyboard()
|
||||
)
|
||||
|
||||
onView(withId(R.id.passwordView)).perform(click()).perform(typeText("WRONGPASS"), closeSoftKeyboard())
|
||||
onView(withId(R.id.passwordView)).perform(click()).perform(
|
||||
typeText("WRONGPASS"),
|
||||
closeSoftKeyboard()
|
||||
)
|
||||
|
||||
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
|
||||
@@ -146,19 +155,23 @@ class LoginActivityEspressoTest {
|
||||
|
||||
onView(withId(R.id.withLogin)).perform(click())
|
||||
|
||||
onView(withId(R.id.loginView)).perform(click()).perform(typeText(username), closeSoftKeyboard())
|
||||
onView(withId(R.id.loginView)).perform(click()).perform(
|
||||
typeText(username),
|
||||
closeSoftKeyboard()
|
||||
)
|
||||
|
||||
onView(withId(R.id.passwordView)).perform(click()).perform(typeText(password), closeSoftKeyboard())
|
||||
onView(withId(R.id.passwordView)).perform(click()).perform(
|
||||
typeText(password),
|
||||
closeSoftKeyboard()
|
||||
)
|
||||
|
||||
onView(withId(R.id.signInButton)).perform(click())
|
||||
|
||||
intended(hasComponent(HomeActivity::class.java.name))
|
||||
|
||||
}
|
||||
|
||||
@After
|
||||
fun releaseIntents() {
|
||||
Intents.release()
|
||||
}
|
||||
|
||||
}
|
||||
+7
-14
@@ -3,13 +3,13 @@ package apps.amine.bou.readerforselfoss
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.preference.PreferenceManager
|
||||
import android.support.test.InstrumentationRegistry.getInstrumentation
|
||||
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.rule.ActivityTestRule
|
||||
import android.support.test.runner.AndroidJUnit4
|
||||
import androidx.test.InstrumentationRegistry.getInstrumentation
|
||||
import androidx.test.espresso.intent.Intents
|
||||
import androidx.test.espresso.intent.Intents.intended
|
||||
import androidx.test.espresso.intent.Intents.times
|
||||
import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
|
||||
import androidx.test.rule.ActivityTestRule
|
||||
import androidx.test.runner.AndroidJUnit4
|
||||
import org.junit.After
|
||||
|
||||
import org.junit.Before
|
||||
@@ -17,7 +17,6 @@ import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class MainActivityEspressoTest {
|
||||
|
||||
@@ -46,9 +45,7 @@ class MainActivityEspressoTest {
|
||||
rule.launchActivity(intent)
|
||||
|
||||
intended(hasComponent(MainActivity::class.java.name))
|
||||
intended(hasComponent(IntroActivity::class.java.name))
|
||||
intended(hasComponent(LoginActivity::class.java.name), times(0))
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -60,14 +57,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()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,31 +1,29 @@
|
||||
package apps.amine.bou.readerforselfoss
|
||||
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import androidx.test.espresso.matcher.ViewMatchers
|
||||
import android.view.View
|
||||
import android.support.design.widget.TextInputLayout
|
||||
import android.support.test.espresso.matcher.ViewMatchers
|
||||
|
||||
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)
|
||||
)
|
||||
@@ -1,12 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="apps.amine.bou.readerforselfoss">
|
||||
package="apps.amine.bou.readerforselfoss"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<!-- For firebase only -->
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application
|
||||
android:name=".MyApp"
|
||||
@@ -23,10 +21,9 @@
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".IntroActivity"
|
||||
android:theme="@style/Theme.Intro">
|
||||
|
||||
<meta-data android:name="android.app.shortcuts"
|
||||
android:resource="@xml/shortcuts" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".LoginActivity"
|
||||
@@ -71,6 +68,14 @@
|
||||
<meta-data
|
||||
android:name="apps.amine.bou.readerforselfoss.utils.glide.SelfSignedGlideModule"
|
||||
android:value="GlideModule" />
|
||||
|
||||
<meta-data android:name="android.webkit.WebView.MetricsOptOut"
|
||||
android:value="true" />
|
||||
|
||||
<meta-data android:name="android.webkit.WebView.EnableSafeBrowsing"
|
||||
android:value="true" />
|
||||
|
||||
<meta-data android:name="android.max_aspect" android:value="2.1" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
</manifest>
|
||||
@@ -1,10 +1,11 @@
|
||||
package apps.amine.bou.readerforselfoss
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.preference.PreferenceManager
|
||||
import android.support.constraint.ConstraintLayout
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import android.view.View
|
||||
import android.widget.AdapterView
|
||||
import android.widget.ArrayAdapter
|
||||
@@ -16,6 +17,8 @@ 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.themes.AppColors
|
||||
import apps.amine.bou.readerforselfoss.themes.Toppings
|
||||
import apps.amine.bou.readerforselfoss.utils.Config
|
||||
import apps.amine.bou.readerforselfoss.utils.isBaseUrlValid
|
||||
import com.ftinc.scoop.Scoop
|
||||
@@ -23,30 +26,70 @@ import kotlinx.android.synthetic.main.activity_add_source.*
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
import android.graphics.PorterDuff
|
||||
|
||||
|
||||
|
||||
class AddSourceActivity : AppCompatActivity() {
|
||||
|
||||
private var mSpoutsValue: String? = null
|
||||
private lateinit var api: SelfossApi
|
||||
|
||||
private lateinit var appColors: AppColors
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
appColors = AppColors(this@AddSourceActivity)
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
Scoop.getInstance().apply(this)
|
||||
|
||||
setContentView(R.layout.activity_add_source)
|
||||
|
||||
val scoop = Scoop.getInstance()
|
||||
scoop.bind(this, Toppings.PRIMARY.value, toolbar)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.value)
|
||||
}
|
||||
|
||||
val drawable = nameInput.background
|
||||
drawable.setColorFilter(appColors.colorAccent, PorterDuff.Mode.SRC_ATOP)
|
||||
|
||||
|
||||
// TODO: clean
|
||||
if(Build.VERSION.SDK_INT > 16) {
|
||||
nameInput.background = drawable
|
||||
} else{
|
||||
nameInput.setBackgroundDrawable(drawable)
|
||||
}
|
||||
|
||||
val drawable1 = sourceUri.background
|
||||
drawable1.setColorFilter(appColors.colorAccent, PorterDuff.Mode.SRC_ATOP)
|
||||
|
||||
if(Build.VERSION.SDK_INT > 16) {
|
||||
sourceUri.background = drawable1
|
||||
} else{
|
||||
sourceUri.setBackgroundDrawable(drawable1)
|
||||
}
|
||||
|
||||
val drawable2 = tags.background
|
||||
drawable2.setColorFilter(appColors.colorAccent, PorterDuff.Mode.SRC_ATOP)
|
||||
|
||||
if(Build.VERSION.SDK_INT > 16) {
|
||||
tags.background = drawable2
|
||||
} else{
|
||||
tags.setBackgroundDrawable(drawable2)
|
||||
}
|
||||
|
||||
setSupportActionBar(toolbar)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
supportActionBar?.setDisplayShowHomeEnabled(true)
|
||||
|
||||
var api: SelfossApi? = null
|
||||
|
||||
try {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
api = SelfossApi(
|
||||
this,
|
||||
this@AddSourceActivity,
|
||||
prefs.getBoolean("isSelfSignedCert", false),
|
||||
prefs.getBoolean("should_log_everything", false)
|
||||
this,
|
||||
this@AddSourceActivity,
|
||||
prefs.getBoolean("isSelfSignedCert", false),
|
||||
prefs.getBoolean("should_log_everything", false)
|
||||
)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
mustLoginToAddSource()
|
||||
@@ -54,10 +97,15 @@ class AddSourceActivity : AppCompatActivity() {
|
||||
|
||||
maybeGetDetailsFromIntentSharing(intent, sourceUri, nameInput)
|
||||
|
||||
saveBtn.setTextColor(appColors.colorAccent)
|
||||
|
||||
saveBtn.setOnClickListener {
|
||||
handleSaveSource(tags, nameInput.text.toString(), sourceUri.text.toString(), api!!)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
val config = Config(this)
|
||||
|
||||
if (config.baseUrl.isEmpty() || !config.baseUrl.isBaseUrlValid()) {
|
||||
@@ -67,12 +115,19 @@ class AddSourceActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSpoutsSpinner(spoutsSpinner: Spinner, api: SelfossApi?, mProgress: ProgressBar, formContainer: ConstraintLayout) {
|
||||
private fun handleSpoutsSpinner(
|
||||
spoutsSpinner: Spinner,
|
||||
api: SelfossApi?,
|
||||
mProgress: ProgressBar,
|
||||
formContainer: ConstraintLayout
|
||||
) {
|
||||
val spoutsKV = HashMap<String, String>()
|
||||
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]
|
||||
override fun onItemSelected(adapterView: AdapterView<*>, view: View?, i: Int, l: Long) {
|
||||
if (view != null) {
|
||||
val spoutName = (view as TextView).text.toString()
|
||||
mSpoutsValue = spoutsKV[spoutName]
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNothingSelected(adapterView: AdapterView<*>) {
|
||||
@@ -82,26 +137,29 @@ class AddSourceActivity : AppCompatActivity() {
|
||||
|
||||
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>>) {
|
||||
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)
|
||||
spoutsKV[value.name] = key
|
||||
}
|
||||
|
||||
mProgress.visibility = View.GONE
|
||||
formContainer.visibility = View.VISIBLE
|
||||
|
||||
val spinnerArrayAdapter =
|
||||
ArrayAdapter(
|
||||
this@AddSourceActivity,
|
||||
android.R.layout.simple_spinner_item,
|
||||
itemsStrings)
|
||||
ArrayAdapter(
|
||||
this@AddSourceActivity,
|
||||
android.R.layout.simple_spinner_item,
|
||||
itemsStrings
|
||||
)
|
||||
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
spoutsSpinner.adapter = spinnerArrayAdapter
|
||||
|
||||
} else {
|
||||
handleProblemWithSpouts()
|
||||
}
|
||||
@@ -112,13 +170,21 @@ class AddSourceActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
private fun handleProblemWithSpouts() {
|
||||
Toast.makeText(this@AddSourceActivity, R.string.cant_get_spouts, Toast.LENGTH_SHORT).show()
|
||||
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) {
|
||||
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))
|
||||
@@ -134,28 +200,40 @@ class AddSourceActivity : AppCompatActivity() {
|
||||
|
||||
private fun handleSaveSource(tags: EditText, title: String, url: String, api: SelfossApi) {
|
||||
|
||||
val sourceDetailsAvailable = 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!!,
|
||||
tags.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()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,60 +0,0 @@
|
||||
package apps.amine.bou.readerforselfoss
|
||||
|
||||
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)
|
||||
|
||||
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
|
||||
|
||||
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.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() {
|
||||
super.onFinish()
|
||||
val getPrefs = PreferenceManager.getDefaultSharedPreferences(baseContext)
|
||||
val e = getPrefs.edit()
|
||||
e.putBoolean("firstStart", false)
|
||||
e.apply()
|
||||
val intent = Intent(this, LoginActivity::class.java)
|
||||
startActivity(intent)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,8 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.support.v7.widget.Toolbar
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import android.text.TextUtils
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
@@ -18,19 +17,18 @@ 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.themes.AppColors
|
||||
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 apps.amine.bou.readerforselfoss.utils.maybeHandleSilentException
|
||||
import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible
|
||||
import com.mikepenz.aboutlibraries.Libs
|
||||
import com.mikepenz.aboutlibraries.LibsBuilder
|
||||
import kotlinx.android.synthetic.main.activity_login.*
|
||||
import org.acra.ACRA
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
import kotlinx.android.synthetic.main.activity_login.*
|
||||
|
||||
|
||||
|
||||
class LoginActivity : AppCompatActivity() {
|
||||
|
||||
@@ -41,15 +39,15 @@ class LoginActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var settings: SharedPreferences
|
||||
private lateinit var editor: SharedPreferences.Editor
|
||||
private lateinit var firebaseAnalytics: FirebaseAnalytics
|
||||
private lateinit var userIdentifier: String
|
||||
private var logErrors: Boolean = false
|
||||
|
||||
|
||||
private lateinit var appColors: AppColors
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
appColors = AppColors(this@LoginActivity)
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
Scoop.getInstance().apply(this)
|
||||
|
||||
setContentView(R.layout.activity_login)
|
||||
|
||||
setSupportActionBar(toolbar)
|
||||
@@ -67,8 +65,6 @@ class LoginActivity : AppCompatActivity() {
|
||||
goToMain()
|
||||
}
|
||||
|
||||
firebaseAnalytics = FirebaseAnalytics.getInstance(this)
|
||||
|
||||
handleActions()
|
||||
}
|
||||
|
||||
@@ -81,13 +77,15 @@ class LoginActivity : AppCompatActivity() {
|
||||
warningText.visibility = visi
|
||||
}
|
||||
|
||||
passwordView.setOnEditorActionListener(TextView.OnEditorActionListener { _, id, _ ->
|
||||
if (id == R.id.loginView || id == EditorInfo.IME_NULL) {
|
||||
attemptLogin()
|
||||
return@OnEditorActionListener true
|
||||
passwordView.setOnEditorActionListener(
|
||||
TextView.OnEditorActionListener { _, id, _ ->
|
||||
if (id == R.id.loginView || id == EditorInfo.IME_NULL) {
|
||||
attemptLogin()
|
||||
return@OnEditorActionListener true
|
||||
}
|
||||
false
|
||||
}
|
||||
false
|
||||
})
|
||||
)
|
||||
|
||||
signInButton.setOnClickListener { attemptLogin() }
|
||||
|
||||
@@ -114,9 +112,10 @@ class LoginActivity : AppCompatActivity() {
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -156,9 +155,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
|
||||
}
|
||||
@@ -191,72 +191,87 @@ class LoginActivity : AppCompatActivity() {
|
||||
editor.putBoolean("isSelfSignedCert", isWithSelfSignedCert)
|
||||
editor.apply()
|
||||
|
||||
val api = SelfossApi(this, this@LoginActivity, isWithSelfSignedCert, isWithSelfSignedCert)
|
||||
api.login().enqueue(object : Callback<SuccessResponse> {
|
||||
private fun preferenceError(t: Throwable) {
|
||||
editor.remove("url")
|
||||
editor.remove("login")
|
||||
editor.remove("httpUserName")
|
||||
editor.remove("password")
|
||||
editor.remove("httpPassword")
|
||||
editor.apply()
|
||||
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)
|
||||
}
|
||||
val api = SelfossApi(
|
||||
this,
|
||||
this@LoginActivity,
|
||||
isWithSelfSignedCert,
|
||||
isWithSelfSignedCert
|
||||
)
|
||||
|
||||
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {
|
||||
if (response.body() != null && response.body()!!.isSuccess) {
|
||||
firebaseAnalytics.logEvent(FirebaseAnalytics.Event.LOGIN, Bundle())
|
||||
goToMain()
|
||||
} else {
|
||||
preferenceError(Exception("No response body..."))
|
||||
if (this@LoginActivity.isNetworkAccessible(this@LoginActivity.findViewById(R.id.loginForm))) {
|
||||
api.login().enqueue(object : Callback<SuccessResponse> {
|
||||
private fun preferenceError(t: Throwable) {
|
||||
editor.remove("url")
|
||||
editor.remove("login")
|
||||
editor.remove("httpUserName")
|
||||
editor.remove("password")
|
||||
editor.remove("httpPassword")
|
||||
editor.apply()
|
||||
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) {
|
||||
ACRA.getErrorReporter().maybeHandleSilentException(t, this@LoginActivity)
|
||||
Toast.makeText(
|
||||
this@LoginActivity,
|
||||
t.message,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
showProgress(false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
|
||||
preferenceError(t)
|
||||
}
|
||||
})
|
||||
override fun onResponse(
|
||||
call: Call<SuccessResponse>,
|
||||
response: Response<SuccessResponse>
|
||||
) {
|
||||
if (response.body() != null && response.body()!!.isSuccess) {
|
||||
goToMain()
|
||||
} else {
|
||||
preferenceError(Exception("No response body..."))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
|
||||
preferenceError(t)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
showProgress(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun showProgress(show: Boolean) {
|
||||
val shortAnimTime = resources.getInteger(android.R.integer.config_shortAnimTime)
|
||||
|
||||
loginForm.visibility = if (show) View.GONE else View.VISIBLE
|
||||
loginForm
|
||||
.animate()
|
||||
.setDuration(shortAnimTime.toLong())
|
||||
.alpha(
|
||||
if (show) 0F else 1F
|
||||
).setListener(object : AnimatorListenerAdapter() {
|
||||
.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
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
loginProgress.visibility = if (show) View.VISIBLE else View.GONE
|
||||
loginProgress
|
||||
.animate()
|
||||
.setDuration(shortAnimTime.toLong())
|
||||
.alpha(
|
||||
if (show) 1F else 0F
|
||||
).setListener(object : AnimatorListenerAdapter() {
|
||||
.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 {
|
||||
@@ -269,10 +284,10 @@ 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 -> {
|
||||
|
||||
@@ -3,9 +3,7 @@ package apps.amine.bou.readerforselfoss
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.preference.PreferenceManager
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
@@ -13,15 +11,9 @@ class MainActivity : AppCompatActivity() {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
if (PreferenceManager.getDefaultSharedPreferences(baseContext).getBoolean("firstStart", true)) {
|
||||
val i = Intent(this@MainActivity, IntroActivity::class.java)
|
||||
startActivity(i)
|
||||
} else {
|
||||
val intent = Intent(this, LoginActivity::class.java)
|
||||
startActivity(intent)
|
||||
}
|
||||
val intent = Intent(this, LoginActivity::class.java)
|
||||
|
||||
startActivity(intent)
|
||||
finish()
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,39 +1,55 @@
|
||||
package apps.amine.bou.readerforselfoss
|
||||
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.preference.PreferenceManager
|
||||
import android.support.multidex.MultiDexApplication
|
||||
import androidx.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 org.acra.ACRA
|
||||
import org.acra.ReportField
|
||||
import org.acra.annotation.AcraCore
|
||||
import org.acra.annotation.AcraDialog
|
||||
import org.acra.annotation.AcraHttpSender
|
||||
import org.acra.sender.HttpSender
|
||||
import java.io.IOException
|
||||
import java.util.UUID.randomUUID
|
||||
|
||||
|
||||
|
||||
|
||||
@AcraHttpSender(uri = "http://amine-bou.fr:5984/acra-selfoss/_design/acra-storage/_update/report",
|
||||
basicAuthLogin = "selfoss",
|
||||
basicAuthPassword = "selfoss",
|
||||
httpMethod = HttpSender.Method.PUT)
|
||||
@AcraDialog(resText = R.string.crash_dialog_text,
|
||||
resCommentPrompt = R.string.crash_dialog_comment,
|
||||
resTheme = android.R.style.Theme_DeviceDefault_Dialog)
|
||||
@AcraCore(reportContent = [ReportField.REPORT_ID, ReportField.INSTALLATION_ID,
|
||||
ReportField.APP_VERSION_CODE, ReportField.APP_VERSION_NAME,
|
||||
ReportField.BUILD, ReportField.ANDROID_VERSION, ReportField.BRAND, ReportField.PHONE_MODEL,
|
||||
ReportField.AVAILABLE_MEM_SIZE, ReportField.TOTAL_MEM_SIZE,
|
||||
ReportField.STACK_TRACE, ReportField.APPLICATION_LOG, ReportField.LOGCAT,
|
||||
ReportField.INITIAL_CONFIGURATION, ReportField.CRASH_CONFIGURATION, ReportField.IS_SILENT,
|
||||
ReportField.USER_APP_START_DATE, ReportField.USER_COMMENT, ReportField.USER_CRASH_DATE, ReportField.USER_EMAIL, ReportField.CUSTOM_DATA],
|
||||
buildConfigClass = BuildConfig::class)
|
||||
class MyApp : MultiDexApplication() {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
Fabric.with(this, Crashlytics())
|
||||
|
||||
initAmplify()
|
||||
|
||||
initCache()
|
||||
|
||||
val prefs = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
|
||||
if (prefs.getString("unique_id", "").isEmpty()) {
|
||||
val editor = prefs.edit()
|
||||
@@ -47,30 +63,52 @@ class MyApp : MultiDexApplication() {
|
||||
|
||||
tryToHandleBug()
|
||||
|
||||
handleNotificationChannels()
|
||||
}
|
||||
|
||||
private fun handleNotificationChannels() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
val name = getString(R.string.notification_channel_sync)
|
||||
val importance = NotificationManager.IMPORTANCE_LOW
|
||||
val mChannel = NotificationChannel(Config.syncChannelId, name, importance)
|
||||
|
||||
val newItemsChannelname = getString(R.string.new_items_channel_sync)
|
||||
val newItemsChannelimportance = NotificationManager.IMPORTANCE_DEFAULT
|
||||
val newItemsChannelmChannel = NotificationChannel(Config.newItemsChannelId, newItemsChannelname, newItemsChannelimportance)
|
||||
|
||||
notificationManager.createNotificationChannel(mChannel)
|
||||
notificationManager.createNotificationChannel(newItemsChannelmChannel)
|
||||
}
|
||||
}
|
||||
|
||||
override fun attachBaseContext(base: Context?) {
|
||||
super.attachBaseContext(base)
|
||||
val prefs = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
|
||||
ACRA.init(this)
|
||||
ACRA.getErrorReporter().putCustomData("unique_id", prefs.getString("unique_id", ""))
|
||||
|
||||
}
|
||||
|
||||
private fun initAmplify() {
|
||||
Amplify.initSharedInstance(this)
|
||||
.setPositiveFeedbackCollectors(GooglePlayStoreFeedbackCollector())
|
||||
.setCriticalFeedbackCollectors(DefaultEmailFeedbackCollector(BuildConfig.FEEDBACK_EMAIL))
|
||||
.setCriticalFeedbackCollectors(DefaultEmailFeedbackCollector(Config.feedbackEmail))
|
||||
.applyAllDefaultRules()
|
||||
}
|
||||
|
||||
private fun initCache() {
|
||||
try {
|
||||
Reservoir.init(this, 8192) //in bytes
|
||||
} catch (e: IOException) {
|
||||
//failure
|
||||
}
|
||||
}
|
||||
|
||||
private fun initDrawerImageLoader() {
|
||||
DrawerImageLoader.init(object : AbstractDrawerImageLoader() {
|
||||
override fun set(imageView: ImageView?, uri: Uri?, placeholder: Drawable?, tag: String?) {
|
||||
override fun set(
|
||||
imageView: ImageView?,
|
||||
uri: Uri?,
|
||||
placeholder: Drawable?,
|
||||
tag: String?
|
||||
) {
|
||||
Glide.with(imageView?.context)
|
||||
.load(uri)
|
||||
.apply(RequestOptions.fitCenterTransform()
|
||||
.placeholder(placeholder))
|
||||
.apply(RequestOptions.fitCenterTransform().placeholder(placeholder))
|
||||
.into(imageView)
|
||||
}
|
||||
|
||||
@@ -87,19 +125,7 @@ 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)
|
||||
.addFlavor(getString(R.string.default_dark_theme), R.style.NoBarDark, false)
|
||||
.setSharedPreferences(PreferenceManager.getDefaultSharedPreferences(this))
|
||||
.initialize()
|
||||
}
|
||||
@@ -108,11 +134,13 @@ class MyApp : MultiDexApplication() {
|
||||
val oldHandler = Thread.getDefaultUncaughtExceptionHandler()
|
||||
|
||||
Thread.setDefaultUncaughtExceptionHandler { thread, e ->
|
||||
if (e is java.lang.NoClassDefFoundError && e.stackTrace.asList().any { it.toString().contains("android.view.ViewDebug") })
|
||||
if (e is java.lang.NoClassDefFoundError && e.stackTrace.asList().any {
|
||||
it.toString().contains("android.view.ViewDebug")
|
||||
}) {
|
||||
Unit
|
||||
else
|
||||
} else {
|
||||
oldHandler.uncaughtException(thread, e)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,190 +1,323 @@
|
||||
package apps.amine.bou.readerforselfoss
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
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 androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.FragmentStatePagerAdapter
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.viewpager.widget.ViewPager
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
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 android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.room.Room
|
||||
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.fragments.ArticleFragment
|
||||
import apps.amine.bou.readerforselfoss.persistence.database.AppDatabase
|
||||
import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity
|
||||
import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_1_2
|
||||
import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_2_3
|
||||
import apps.amine.bou.readerforselfoss.themes.AppColors
|
||||
import apps.amine.bou.readerforselfoss.themes.Toppings
|
||||
import apps.amine.bou.readerforselfoss.transformers.DepthPageTransformer
|
||||
import apps.amine.bou.readerforselfoss.utils.maybeHandleSilentException
|
||||
import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible
|
||||
import apps.amine.bou.readerforselfoss.utils.persistence.toEntity
|
||||
import apps.amine.bou.readerforselfoss.utils.succeeded
|
||||
import apps.amine.bou.readerforselfoss.utils.toggleStar
|
||||
import com.ftinc.scoop.Scoop
|
||||
import com.github.rubensousa.floatingtoolbar.FloatingToolbar
|
||||
import org.sufficientlysecure.htmltextview.HtmlHttpImageGetter
|
||||
import kotlinx.android.synthetic.main.activity_reader.*
|
||||
import me.relex.circleindicator.CircleIndicator
|
||||
import org.acra.ACRA
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
import kotlinx.android.synthetic.main.activity_reader.*
|
||||
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
class ReaderActivity : AppCompatActivity() {
|
||||
private lateinit var mCustomTabActivityHelper: CustomTabActivityHelper
|
||||
//private lateinit var content: HtmlTextView
|
||||
private lateinit var url: String
|
||||
private lateinit var contentText: String
|
||||
private lateinit var contentSource: String
|
||||
private lateinit var contentImage: String
|
||||
private lateinit var contentTitle: String
|
||||
private lateinit var fab: FloatingActionButton
|
||||
|
||||
private var markOnScroll: Boolean = false
|
||||
private var debugReadingItems: Boolean = false
|
||||
private var currentItem: Int = 0
|
||||
private lateinit var userIdentifier: String
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
mCustomTabActivityHelper.unbindCustomTabsService(this)
|
||||
private lateinit var api: SelfossApi
|
||||
|
||||
private lateinit var toolbarMenu: Menu
|
||||
|
||||
private lateinit var db: AppDatabase
|
||||
|
||||
private fun showMenuItem(willAddToFavorite: Boolean) {
|
||||
toolbarMenu.findItem(R.id.save).isVisible = willAddToFavorite
|
||||
toolbarMenu.findItem(R.id.unsave).isVisible = !willAddToFavorite
|
||||
}
|
||||
|
||||
private fun canFavorite() {
|
||||
showMenuItem(true)
|
||||
}
|
||||
|
||||
private fun canRemoveFromFavorite() {
|
||||
showMenuItem(false)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
Scoop.getInstance().apply(this)
|
||||
|
||||
setContentView(R.layout.activity_reader)
|
||||
|
||||
url = intent.getStringExtra("url")
|
||||
contentText = intent.getStringExtra("content")
|
||||
contentTitle = intent.getStringExtra("title")
|
||||
contentImage = intent.getStringExtra("image")
|
||||
contentSource = intent.getStringExtra("source")
|
||||
db = Room.databaseBuilder(
|
||||
applicationContext,
|
||||
AppDatabase::class.java, "selfoss-database"
|
||||
).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).build()
|
||||
|
||||
fab = findViewById(R.id.fab)
|
||||
val mFloatingToolbar: FloatingToolbar = findViewById(R.id.floatingToolbar)
|
||||
mFloatingToolbar.attachFab(fab)
|
||||
val scoop = Scoop.getInstance()
|
||||
scoop.bind(this, Toppings.PRIMARY.value, toolBar)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.value)
|
||||
}
|
||||
|
||||
val customTabsIntent = this@ReaderActivity.buildCustomTabsIntent()
|
||||
mCustomTabActivityHelper = CustomTabActivityHelper()
|
||||
mCustomTabActivityHelper.bindCustomTabsService(this)
|
||||
setSupportActionBar(toolBar)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
supportActionBar?.setDisplayShowHomeEnabled(true)
|
||||
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
|
||||
mFloatingToolbar.setClickListener(object : FloatingToolbar.ItemClickListener {
|
||||
override fun onItemClick(item: MenuItem) {
|
||||
when (item.itemId) {
|
||||
R.id.more_action -> getContentFromMercury(customTabsIntent, prefs)
|
||||
R.id.share_action -> this@ReaderActivity.shareLink(url)
|
||||
R.id.open_action -> this@ReaderActivity.openItemUrl(
|
||||
url,
|
||||
contentText,
|
||||
contentImage,
|
||||
contentTitle,
|
||||
contentSource,
|
||||
customTabsIntent,
|
||||
false,
|
||||
false,
|
||||
this@ReaderActivity)
|
||||
else -> Unit
|
||||
debugReadingItems = prefs.getBoolean("read_debug", false)
|
||||
userIdentifier = prefs.getString("unique_id", "")
|
||||
markOnScroll = prefs.getBoolean("mark_on_scroll", false)
|
||||
|
||||
api = SelfossApi(
|
||||
this,
|
||||
this@ReaderActivity,
|
||||
prefs.getBoolean("isSelfSignedCert", false),
|
||||
prefs.getBoolean("should_log_everything", false)
|
||||
)
|
||||
|
||||
if (allItems.isEmpty()) {
|
||||
finish()
|
||||
}
|
||||
|
||||
currentItem = intent.getIntExtra("currentItem", 0)
|
||||
|
||||
readItem(allItems[currentItem])
|
||||
|
||||
pager.adapter =
|
||||
ScreenSlidePagerAdapter(supportFragmentManager, AppColors(this@ReaderActivity))
|
||||
pager.currentItem = currentItem
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
notifyAdapter()
|
||||
|
||||
pager.setPageTransformer(true, DepthPageTransformer())
|
||||
(indicator as CircleIndicator).setViewPager(pager)
|
||||
|
||||
pager.addOnPageChangeListener(
|
||||
object : ViewPager.SimpleOnPageChangeListener() {
|
||||
|
||||
override fun onPageSelected(position: Int) {
|
||||
|
||||
if (allItems[position].starred) {
|
||||
canRemoveFromFavorite()
|
||||
} else {
|
||||
canFavorite()
|
||||
}
|
||||
readItem(allItems[pager.currentItem])
|
||||
}
|
||||
}
|
||||
|
||||
override fun onItemLongClick(item: MenuItem?) {
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
if (contentText.isEmptyOrNullOrNullString()) {
|
||||
getContentFromMercury(customTabsIntent, prefs)
|
||||
} else {
|
||||
source.text = contentSource
|
||||
titleView.text = contentTitle
|
||||
tryToHandleHtml(contentText, customTabsIntent, prefs)
|
||||
|
||||
if (!contentImage.isEmptyOrNullOrNullString()) {
|
||||
imageView.visibility = View.VISIBLE
|
||||
Glide
|
||||
.with(baseContext)
|
||||
.asBitmap()
|
||||
.load(contentImage)
|
||||
.apply(RequestOptions.fitCenterTransform())
|
||||
.into(imageView)
|
||||
} else {
|
||||
imageView.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
nestedScrollView.setOnScrollChangeListener(NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
|
||||
if (scrollY > oldScrollY) {
|
||||
fab.hide()
|
||||
} else {
|
||||
if (mFloatingToolbar.isShowing) mFloatingToolbar.hide() else fab.show()
|
||||
}
|
||||
})
|
||||
|
||||
content.movementMethod = LinkMovementMethod.getInstance()
|
||||
}
|
||||
|
||||
private fun getContentFromMercury(customTabsIntent: CustomTabsIntent, prefs: SharedPreferences) {
|
||||
progressBar.visibility = View.VISIBLE
|
||||
val parser = MercuryApi(BuildConfig.MERCURY_KEY, prefs.getBoolean("should_log_everything", false))
|
||||
|
||||
parser.parseUrl(url).enqueue(object : Callback<ParsedContent> {
|
||||
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()
|
||||
}
|
||||
|
||||
fun readItem(item: Item) {
|
||||
if (markOnScroll) {
|
||||
thread {
|
||||
db.itemsDao().delete(item.toEntity())
|
||||
}
|
||||
if (this@ReaderActivity.isNetworkAccessible(this@ReaderActivity.findViewById(R.id.reader_activity_view))) {
|
||||
api.markItem(item.id).enqueue(
|
||||
object : Callback<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}"
|
||||
ACRA.getErrorReporter()
|
||||
.maybeHandleSilentException(Exception(message), this@ReaderActivity)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(
|
||||
call: Call<SuccessResponse>,
|
||||
t: Throwable
|
||||
) {
|
||||
thread {
|
||||
db.itemsDao().insertAllItems(item.toEntity())
|
||||
}
|
||||
if (debugReadingItems) {
|
||||
ACRA.getErrorReporter()
|
||||
.maybeHandleSilentException(t, this@ReaderActivity)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
thread {
|
||||
db.actionsDao().insertAllActions(ActionEntity(item.id, true, false, false, false))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun notifyAdapter() {
|
||||
(pager.adapter as ScreenSlidePagerAdapter).notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
if (markOnScroll) {
|
||||
pager.clearOnPageChangeListeners()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(oldInstanceState: Bundle?) {
|
||||
super.onSaveInstanceState(oldInstanceState)
|
||||
oldInstanceState!!.clear()
|
||||
}
|
||||
|
||||
private inner class ScreenSlidePagerAdapter(fm: FragmentManager, val appColors: AppColors) :
|
||||
FragmentStatePagerAdapter(fm) {
|
||||
|
||||
override fun getCount(): Int {
|
||||
return allItems.size
|
||||
}
|
||||
|
||||
override fun getItem(position: Int): ArticleFragment {
|
||||
return ArticleFragment.newInstance(position, allItems)
|
||||
}
|
||||
|
||||
override fun startUpdate(container: ViewGroup) {
|
||||
super.startUpdate(container)
|
||||
|
||||
container.background = ColorDrawable(
|
||||
ContextCompat.getColor(
|
||||
this@ReaderActivity,
|
||||
appColors.colorBackground
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
val inflater = menuInflater
|
||||
inflater.inflate(R.menu.reader_menu, menu)
|
||||
toolbarMenu = menu
|
||||
|
||||
if (!allItems.isEmpty() && allItems[currentItem].starred) {
|
||||
canRemoveFromFavorite()
|
||||
} else {
|
||||
canFavorite()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
fun afterSave() {
|
||||
allItems[pager.currentItem] =
|
||||
allItems[pager.currentItem].toggleStar()
|
||||
notifyAdapter()
|
||||
canRemoveFromFavorite()
|
||||
}
|
||||
|
||||
fun afterUnsave() {
|
||||
allItems[pager.currentItem] = allItems[pager.currentItem].toggleStar()
|
||||
notifyAdapter()
|
||||
canFavorite()
|
||||
}
|
||||
|
||||
when (item.itemId) {
|
||||
android.R.id.home -> {
|
||||
onBackPressed()
|
||||
return true
|
||||
}
|
||||
R.id.save -> {
|
||||
if (this@ReaderActivity.isNetworkAccessible(null)) {
|
||||
api.starrItem(allItems[pager.currentItem].id)
|
||||
.enqueue(object : Callback<SuccessResponse> {
|
||||
override fun onResponse(
|
||||
call: Call<SuccessResponse>,
|
||||
response: Response<SuccessResponse>
|
||||
) {
|
||||
afterSave()
|
||||
}
|
||||
|
||||
override fun onFailure(
|
||||
call: Call<SuccessResponse>,
|
||||
t: Throwable
|
||||
) {
|
||||
Toast.makeText(
|
||||
baseContext,
|
||||
R.string.cant_mark_favortie,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
thread {
|
||||
db.actionsDao().insertAllActions(ActionEntity(allItems[pager.currentItem].id, false, false, true, false))
|
||||
afterSave()
|
||||
}
|
||||
}
|
||||
}
|
||||
R.id.unsave -> {
|
||||
if (this@ReaderActivity.isNetworkAccessible(null)) {
|
||||
api.unstarrItem(allItems[pager.currentItem].id)
|
||||
.enqueue(object : Callback<SuccessResponse> {
|
||||
override fun onResponse(
|
||||
call: Call<SuccessResponse>,
|
||||
response: Response<SuccessResponse>
|
||||
) {
|
||||
afterUnsave()
|
||||
}
|
||||
|
||||
override fun onFailure(
|
||||
call: Call<SuccessResponse>,
|
||||
t: Throwable
|
||||
) {
|
||||
Toast.makeText(
|
||||
baseContext,
|
||||
R.string.cant_unmark_favortie,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
thread {
|
||||
db.actionsDao().insertAllActions(ActionEntity(allItems[pager.currentItem].id, false, false, false, true))
|
||||
afterUnsave()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
companion object {
|
||||
var allItems: ArrayList<Item> = ArrayList()
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,48 @@
|
||||
package apps.amine.bou.readerforselfoss
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.res.ColorStateList
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.preference.PreferenceManager
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.support.v7.widget.LinearLayoutManager
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import android.widget.Toast
|
||||
import apps.amine.bou.readerforselfoss.adapters.SourcesListAdapter
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.Sources
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.Source
|
||||
import apps.amine.bou.readerforselfoss.themes.AppColors
|
||||
import apps.amine.bou.readerforselfoss.themes.Toppings
|
||||
import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible
|
||||
import com.ftinc.scoop.Scoop
|
||||
import kotlinx.android.synthetic.main.activity_sources.*
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
import kotlinx.android.synthetic.main.activity_sources.*
|
||||
|
||||
class SourcesActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var appColors: AppColors
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
appColors = AppColors(this@SourcesActivity)
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
Scoop.getInstance().apply(this)
|
||||
|
||||
setContentView(R.layout.activity_sources)
|
||||
|
||||
val scoop = Scoop.getInstance()
|
||||
scoop.bind(this, Toppings.PRIMARY.value, toolbar)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.value)
|
||||
}
|
||||
|
||||
setSupportActionBar(toolbar)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
supportActionBar?.setDisplayShowHomeEnabled(true)
|
||||
|
||||
fab.rippleColor = appColors.colorAccentDark
|
||||
fab.backgroundTintList = ColorStateList.valueOf(appColors.colorAccent)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
@@ -39,27 +56,47 @@ class SourcesActivity : AppCompatActivity() {
|
||||
|
||||
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()
|
||||
val api = SelfossApi(
|
||||
this,
|
||||
this@SourcesActivity,
|
||||
prefs.getBoolean("isSelfSignedCert", false),
|
||||
prefs.getBoolean("should_log_everything", false)
|
||||
)
|
||||
var items: ArrayList<Source> = ArrayList()
|
||||
|
||||
recyclerView.setHasFixedSize(true)
|
||||
recyclerView.layoutManager = mLayoutManager
|
||||
|
||||
api.sources.enqueue(object : Callback<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>
|
||||
if (this@SourcesActivity.isNetworkAccessible(this@SourcesActivity.findViewById(R.id.recyclerView))) {
|
||||
api.sources.enqueue(object : Callback<List<Source>> {
|
||||
override fun onResponse(
|
||||
call: Call<List<Source>>,
|
||||
response: Response<List<Source>>
|
||||
) {
|
||||
if (response.body() != null && response.body()!!.isNotEmpty()) {
|
||||
items = response.body() as ArrayList<Source>
|
||||
}
|
||||
val mAdapter = SourcesListAdapter(this@SourcesActivity, items, api)
|
||||
recyclerView.adapter = mAdapter
|
||||
mAdapter.notifyDataSetChanged()
|
||||
if (items.isEmpty()) {
|
||||
Toast.makeText(
|
||||
this@SourcesActivity,
|
||||
R.string.nothing_here,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
val mAdapter = SourcesListAdapter(this@SourcesActivity, items, api)
|
||||
recyclerView.adapter = mAdapter
|
||||
mAdapter.notifyDataSetChanged()
|
||||
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()
|
||||
}
|
||||
})
|
||||
override fun onFailure(call: Call<List<Source>>, t: Throwable) {
|
||||
Toast.makeText(
|
||||
this@SourcesActivity,
|
||||
R.string.cant_get_sources,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fab.setOnClickListener {
|
||||
startActivity(Intent(this@SourcesActivity, AddSourceActivity::class.java))
|
||||
|
||||
@@ -2,54 +2,61 @@ package apps.amine.bou.readerforselfoss.adapters
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.support.design.widget.Snackbar
|
||||
import android.support.v7.widget.CardView
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import androidx.cardview.widget.CardView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import android.text.Html
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView.ScaleType
|
||||
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.persistence.database.AppDatabase
|
||||
import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity
|
||||
import apps.amine.bou.readerforselfoss.themes.AppColors
|
||||
import apps.amine.bou.readerforselfoss.utils.LinkOnTouchListener
|
||||
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.network.isNetworkAccessible
|
||||
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
|
||||
import kotlinx.android.synthetic.main.card_item.view.*
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
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>() {
|
||||
class ItemCardAdapter(
|
||||
override val app: Activity,
|
||||
override var items: ArrayList<Item>,
|
||||
override val api: SelfossApi,
|
||||
override val db: AppDatabase,
|
||||
private val helper: CustomTabActivityHelper,
|
||||
private val internalBrowser: Boolean,
|
||||
private val articleViewer: Boolean,
|
||||
private val fullHeightCards: Boolean,
|
||||
override val appColors: AppColors,
|
||||
override val debugReadingItems: Boolean,
|
||||
override val userIdentifier: String,
|
||||
override val updateItems: (ArrayList<Item>) -> Unit
|
||||
) : ItemsAdapter<ItemCardAdapter.ViewHolder>() {
|
||||
private val c: Context = app.baseContext
|
||||
private val generator: ColorGenerator = ColorGenerator.MATERIAL
|
||||
private val imageMaxHeight: Int =
|
||||
c.resources.getDimension(R.dimen.card_image_max_height).toInt()
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val v = LayoutInflater.from(c).inflate(R.layout.card_item, parent, false) as CardView
|
||||
@@ -62,13 +69,23 @@ class ItemCardAdapter(private val app: Activity,
|
||||
|
||||
holder.mView.favButton.isLiked = itm.starred
|
||||
holder.mView.title.text = Html.fromHtml(itm.title)
|
||||
holder.mView.title.setOnTouchListener(LinkOnTouchListener())
|
||||
|
||||
holder.mView.title.setLinkTextColor(appColors.colorAccent)
|
||||
|
||||
holder.mView.sourceTitleAndDate.text = itm.sourceAndDateText()
|
||||
|
||||
if (!fullHeightCards) {
|
||||
holder.mView.itemImage.maxHeight = imageMaxHeight
|
||||
holder.mView.itemImage.scaleType = ScaleType.CENTER_CROP
|
||||
}
|
||||
|
||||
if (itm.getThumbnail(c).isEmpty()) {
|
||||
holder.mView.itemImage.visibility = View.GONE
|
||||
Glide.with(c).clear(holder.mView.itemImage)
|
||||
holder.mView.itemImage.setImageDrawable(null)
|
||||
} else {
|
||||
holder.mView.itemImage.visibility = View.VISIBLE
|
||||
c.bitmapCenterCrop(itm.getThumbnail(c), holder.mView.itemImage)
|
||||
}
|
||||
|
||||
@@ -76,10 +93,10 @@ class ItemCardAdapter(private val app: Activity,
|
||||
val color = generator.getColor(itm.sourcetitle)
|
||||
|
||||
val drawable =
|
||||
TextDrawable
|
||||
.builder()
|
||||
.round()
|
||||
.build(itm.sourcetitle.toTextDrawableString(), color)
|
||||
TextDrawable
|
||||
.builder()
|
||||
.round()
|
||||
.build(itm.sourcetitle.toTextDrawableString(c), color)
|
||||
holder.mView.sourceImage.setImageDrawable(drawable)
|
||||
} else {
|
||||
c.circularBitmapDrawable(itm.getIcon(c), holder.mView.sourceImage)
|
||||
@@ -92,109 +109,72 @@ class ItemCardAdapter(private val app: Activity,
|
||||
return 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)
|
||||
.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 onFailure(call: Call<SuccessResponse>, t: Throwable) {
|
||||
items.remove(i)
|
||||
notifyItemRemoved(position)
|
||||
doUnmark(i, position)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
val view = s.view
|
||||
val tv: TextView = view.findViewById(android.support.design.R.id.snackbar_text)
|
||||
tv.setTextColor(Color.WHITE)
|
||||
s.show()
|
||||
}
|
||||
|
||||
fun removeItemAtIndex(position: Int) {
|
||||
|
||||
val i = items[position]
|
||||
|
||||
items.remove(i)
|
||||
notifyItemRemoved(position)
|
||||
|
||||
api.markItem(i.id).enqueue(object : Callback<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) {
|
||||
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: CardView) : RecyclerView.ViewHolder(mView) {
|
||||
init {
|
||||
mView.setCardBackgroundColor(appColors.cardBackground)
|
||||
mView.setCardBackgroundColor(appColors.cardBackgroundColor)
|
||||
handleClickListeners()
|
||||
handleCustomTabActions()
|
||||
}
|
||||
|
||||
private fun handleClickListeners() {
|
||||
|
||||
if (!fullHeightCards) {
|
||||
mView.itemImage.maxHeight = c.resources.getDimension(R.dimen.card_image_max_height).toInt()
|
||||
mView.itemImage.scaleType = ScaleType.CENTER_CROP
|
||||
}
|
||||
|
||||
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>) {}
|
||||
if (c.isNetworkAccessible(null)) {
|
||||
api.starrItem(id).enqueue(object : Callback<SuccessResponse> {
|
||||
override fun onResponse(
|
||||
call: Call<SuccessResponse>,
|
||||
response: Response<SuccessResponse>
|
||||
) {
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
|
||||
mView.favButton.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()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
thread {
|
||||
db.actionsDao().insertAllActions(ActionEntity(id, false, false, true, false))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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>) {}
|
||||
if (c.isNetworkAccessible(null)) {
|
||||
api.unstarrItem(id).enqueue(object : Callback<SuccessResponse> {
|
||||
override fun onResponse(
|
||||
call: Call<SuccessResponse>,
|
||||
response: Response<SuccessResponse>
|
||||
) {
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
|
||||
mView.favButton.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()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
thread {
|
||||
db.actionsDao().insertAllActions(ActionEntity(id, false, false, false, true))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -212,15 +192,15 @@ class ItemCardAdapter(private val app: Activity,
|
||||
helper.bindCustomTabsService(app)
|
||||
|
||||
mView.setOnClickListener {
|
||||
c.openItemUrl(items[adapterPosition].getLinkDecoded(),
|
||||
items[adapterPosition].content,
|
||||
items[adapterPosition].getThumbnail(c),
|
||||
items[adapterPosition].title,
|
||||
items[adapterPosition].sourceAndDateText(),
|
||||
customTabsIntent,
|
||||
internalBrowser,
|
||||
articleViewer,
|
||||
app)
|
||||
c.openItemUrl(
|
||||
items,
|
||||
adapterPosition,
|
||||
items[adapterPosition].getLinkDecoded(),
|
||||
customTabsIntent,
|
||||
internalBrowser,
|
||||
articleViewer,
|
||||
app
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
package apps.amine.bou.readerforselfoss.adapters
|
||||
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.support.constraint.ConstraintLayout
|
||||
import android.support.design.widget.Snackbar
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import android.text.Html
|
||||
import android.text.Spannable
|
||||
import android.text.style.ClickableSpan
|
||||
import android.util.TypedValue
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
@@ -18,6 +18,9 @@ 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.persistence.database.AppDatabase
|
||||
import apps.amine.bou.readerforselfoss.themes.AppColors
|
||||
import apps.amine.bou.readerforselfoss.utils.LinkOnTouchListener
|
||||
import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent
|
||||
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
|
||||
import apps.amine.bou.readerforselfoss.utils.glide.bitmapCenterCrop
|
||||
@@ -26,34 +29,40 @@ 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.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
|
||||
import kotlinx.android.synthetic.main.list_item.view.*
|
||||
|
||||
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>() {
|
||||
class ItemListAdapter(
|
||||
override val app: Activity,
|
||||
override var items: ArrayList<Item>,
|
||||
override val api: SelfossApi,
|
||||
override val db: AppDatabase,
|
||||
private val helper: CustomTabActivityHelper,
|
||||
private val internalBrowser: Boolean,
|
||||
private val articleViewer: Boolean,
|
||||
override val debugReadingItems: Boolean,
|
||||
override val userIdentifier: String,
|
||||
override val appColors: AppColors,
|
||||
override val updateItems: (ArrayList<Item>) -> Unit
|
||||
) : ItemsAdapter<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,21 +70,26 @@ class ItemListAdapter(private val app: Activity,
|
||||
val itm = items[position]
|
||||
|
||||
|
||||
holder.mView.favButton.isLiked = itm.starred
|
||||
holder.mView.title.text = Html.fromHtml(itm.title)
|
||||
|
||||
holder.mView.title.setOnTouchListener(LinkOnTouchListener())
|
||||
|
||||
holder.mView.title.setLinkTextColor(appColors.colorAccent)
|
||||
|
||||
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()
|
||||
TypedValue.COMPLEX_UNIT_DIP, marginInInt.toFloat(), c.resources
|
||||
.displayMetrics
|
||||
).toInt()
|
||||
|
||||
val params = holder.mView.itemImage.layoutParams as ViewGroup.MarginLayoutParams
|
||||
params.height = sizeInDp
|
||||
@@ -85,14 +99,13 @@ class ItemListAdapter(private val app: Activity,
|
||||
|
||||
if (itm.getIcon(c).isEmpty()) {
|
||||
val color = generator.getColor(itm.sourcetitle)
|
||||
val textDrawable = StringBuilder()
|
||||
for (s in itm.sourcetitle.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) {
|
||||
textDrawable.append(s[0])
|
||||
}
|
||||
|
||||
val builder = TextDrawable.builder().round()
|
||||
val drawable =
|
||||
TextDrawable
|
||||
.builder()
|
||||
.round()
|
||||
.build(itm.sourcetitle.toTextDrawableString(c), color)
|
||||
|
||||
val drawable = builder.build(textDrawable.toString(), color)
|
||||
holder.mView.itemImage.setImageDrawable(drawable)
|
||||
} else {
|
||||
c.circularBitmapDrawable(itm.getIcon(c), holder.mView.itemImage)
|
||||
@@ -100,171 +113,30 @@ class ItemListAdapter(private val app: Activity,
|
||||
} else {
|
||||
c.bitmapCenterCrop(itm.getThumbnail(c), holder.mView.itemImage)
|
||||
}
|
||||
|
||||
if (bars[position]) holder.mView.actionBar.visibility = View.VISIBLE else holder.mView.actionBar.visibility = View.GONE
|
||||
|
||||
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)
|
||||
.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 onFailure(call: Call<SuccessResponse>, t: Throwable) {
|
||||
items.remove(i)
|
||||
notifyItemRemoved(position)
|
||||
doUnmark(i, position)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
val view = s.view
|
||||
val tv: TextView = view.findViewById(android.support.design.R.id.snackbar_text)
|
||||
tv.setTextColor(Color.WHITE)
|
||||
s.show()
|
||||
}
|
||||
|
||||
fun removeItemAtIndex(position: Int) {
|
||||
|
||||
val i = items[position]
|
||||
|
||||
items.remove(i)
|
||||
notifyItemRemoved(position)
|
||||
|
||||
api.markItem(i.id).enqueue(object : Callback<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) {
|
||||
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) {
|
||||
|
||||
init {
|
||||
handleClickListeners()
|
||||
handleCustomTabActions()
|
||||
}
|
||||
|
||||
private fun handleClickListeners() {
|
||||
|
||||
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 onFailure(call: Call<SuccessResponse>, t: Throwable) {
|
||||
mView.favButton.isLiked = false
|
||||
Toast.makeText(c, R.string.cant_mark_favortie, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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 onFailure(call: Call<SuccessResponse>, t: Throwable) {
|
||||
mView.favButton.isLiked = true
|
||||
Toast.makeText(c, R.string.cant_unmark_favortie, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
mView.shareBtn.setOnClickListener {
|
||||
c.shareLink(items[adapterPosition].getLinkDecoded())
|
||||
}
|
||||
|
||||
mView.browserBtn.setOnClickListener {
|
||||
c.openInBrowserAsNewTask(items[adapterPosition])
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun handleCustomTabActions() {
|
||||
val customTabsIntent = c.buildCustomTabsIntent()
|
||||
helper.bindCustomTabsService(app)
|
||||
|
||||
|
||||
if (!clickBehavior) {
|
||||
mView.setOnClickListener {
|
||||
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()
|
||||
true
|
||||
}
|
||||
} else {
|
||||
mView.setOnClickListener { actionBarShowHide() }
|
||||
mView.setOnLongClickListener {
|
||||
c.openItemUrl(items[adapterPosition].getLinkDecoded(),
|
||||
items[adapterPosition].content,
|
||||
items[adapterPosition].getThumbnail(c),
|
||||
items[adapterPosition].title,
|
||||
items[adapterPosition].sourceAndDateText(),
|
||||
customTabsIntent,
|
||||
internalBrowser,
|
||||
articleViewer,
|
||||
app)
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun actionBarShowHide() {
|
||||
bars[adapterPosition] = true
|
||||
if (mView.actionBar.visibility == View.GONE) {
|
||||
mView.actionBar.visibility = View.VISIBLE
|
||||
} else {
|
||||
mView.actionBar.visibility = View.GONE
|
||||
mView.setOnClickListener {
|
||||
c.openItemUrl(
|
||||
items,
|
||||
adapterPosition,
|
||||
items[adapterPosition].getLinkDecoded(),
|
||||
customTabsIntent,
|
||||
internalBrowser,
|
||||
articleViewer,
|
||||
app
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
package apps.amine.bou.readerforselfoss.adapters
|
||||
|
||||
import android.app.Activity
|
||||
import android.graphics.Color
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
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.persistence.database.AppDatabase
|
||||
import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity
|
||||
import apps.amine.bou.readerforselfoss.themes.AppColors
|
||||
import apps.amine.bou.readerforselfoss.utils.maybeHandleSilentException
|
||||
import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible
|
||||
import apps.amine.bou.readerforselfoss.utils.persistence.toEntity
|
||||
import apps.amine.bou.readerforselfoss.utils.succeeded
|
||||
import org.acra.ACRA
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapter<VH>() {
|
||||
abstract var items: ArrayList<Item>
|
||||
abstract val api: SelfossApi
|
||||
abstract val db: AppDatabase
|
||||
abstract val debugReadingItems: Boolean
|
||||
abstract val userIdentifier: String
|
||||
abstract val app: Activity
|
||||
abstract val appColors: AppColors
|
||||
abstract val updateItems: (ArrayList<Item>) -> Unit
|
||||
|
||||
fun updateAllItems(newItems: ArrayList<Item>) {
|
||||
items = newItems
|
||||
notifyDataSetChanged()
|
||||
updateItems(items)
|
||||
}
|
||||
|
||||
private fun doUnmark(i: Item, position: Int) {
|
||||
val s = Snackbar
|
||||
.make(
|
||||
app.findViewById(R.id.coordLayout),
|
||||
R.string.marked_as_read,
|
||||
Snackbar.LENGTH_LONG
|
||||
)
|
||||
.setAction(R.string.undo_string) {
|
||||
items.add(position, i)
|
||||
thread {
|
||||
db.itemsDao().insertAllItems(i.toEntity())
|
||||
}
|
||||
notifyItemInserted(position)
|
||||
updateItems(items)
|
||||
|
||||
if (app.isNetworkAccessible(null)) {
|
||||
api.unmarkItem(i.id).enqueue(object : Callback<SuccessResponse> {
|
||||
override fun onResponse(
|
||||
call: Call<SuccessResponse>,
|
||||
response: Response<SuccessResponse>
|
||||
) {
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
|
||||
items.remove(i)
|
||||
thread {
|
||||
db.itemsDao().delete(i.toEntity())
|
||||
}
|
||||
notifyItemRemoved(position)
|
||||
updateItems(items)
|
||||
doUnmark(i, position)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
thread {
|
||||
db.actionsDao().deleteReadActionForArticle(i.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val view = s.view
|
||||
val tv: TextView = view.findViewById(com.google.android.material.R.id.snackbar_text)
|
||||
tv.setTextColor(Color.WHITE)
|
||||
s.show()
|
||||
}
|
||||
|
||||
fun removeItemAtIndex(position: Int) {
|
||||
val i = items[position]
|
||||
items.remove(i)
|
||||
notifyItemRemoved(position)
|
||||
updateItems(items)
|
||||
|
||||
thread {
|
||||
db.itemsDao().delete(i.toEntity())
|
||||
}
|
||||
|
||||
if (app.isNetworkAccessible(null)) {
|
||||
api.markItem(i.id).enqueue(object : Callback<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}"
|
||||
ACRA.getErrorReporter().maybeHandleSilentException(Exception(message), app)
|
||||
Toast.makeText(app.baseContext, message, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
doUnmark(i, position)
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
|
||||
if (debugReadingItems) {
|
||||
ACRA.getErrorReporter().maybeHandleSilentException(t, app)
|
||||
Toast.makeText(app.baseContext, 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)
|
||||
updateItems(items)
|
||||
|
||||
thread {
|
||||
db.itemsDao().insertAllItems(i.toEntity())
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
thread {
|
||||
db.actionsDao().insertAllActions(ActionEntity(i.id, true, false, false, false))
|
||||
doUnmark(i, position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addItemAtIndex(item: Item, position: Int) {
|
||||
items.add(position, item)
|
||||
notifyItemInserted(position)
|
||||
updateItems(items)
|
||||
|
||||
}
|
||||
|
||||
fun addItemsAtEnd(newItems: List<Item>) {
|
||||
val oldSize = items.size
|
||||
items.addAll(newItems)
|
||||
notifyItemRangeInserted(oldSize, newItems.size)
|
||||
updateItems(items)
|
||||
|
||||
}
|
||||
}
|
||||
@@ -2,36 +2,40 @@ package apps.amine.bou.readerforselfoss.adapters
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.support.constraint.ConstraintLayout
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.recyclerview.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 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.Source
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
|
||||
import apps.amine.bou.readerforselfoss.utils.glide.circularBitmapDrawable
|
||||
import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible
|
||||
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
|
||||
import kotlinx.android.synthetic.main.source_list_item.view.*
|
||||
|
||||
|
||||
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<Source>,
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -42,25 +46,21 @@ class SourcesListAdapter(private val app: Activity,
|
||||
val color = generator.getColor(itm.title)
|
||||
|
||||
val drawable =
|
||||
TextDrawable
|
||||
.builder()
|
||||
.round()
|
||||
.build(itm.title.toTextDrawableString(), color)
|
||||
holder.itemImage.setImageDrawable(drawable)
|
||||
TextDrawable
|
||||
.builder()
|
||||
.round()
|
||||
.build(itm.title.toTextDrawableString(c), color)
|
||||
holder.mView.itemImage.setImageDrawable(drawable)
|
||||
} else {
|
||||
c.circularBitmapDrawable(itm.getIcon(c), holder.itemImage)
|
||||
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 itemImage: ImageView
|
||||
lateinit var sourceTitle: TextView
|
||||
|
||||
init {
|
||||
handleClickListeners()
|
||||
@@ -71,25 +71,36 @@ class SourcesListAdapter(private val app: Activity,
|
||||
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>) {
|
||||
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()
|
||||
if (c.isNetworkAccessible(null)) {
|
||||
val (id) = items[adapterPosition]
|
||||
api.deleteSource(id).enqueue(object : Callback<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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
|
||||
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()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,27 +7,26 @@ import retrofit2.Call
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
|
||||
|
||||
|
||||
class MercuryApi(private val key: String, shouldLog: Boolean) {
|
||||
class MercuryApi(shouldLog: Boolean) {
|
||||
private val service: MercuryService
|
||||
|
||||
init {
|
||||
|
||||
val interceptor = HttpLoggingInterceptor()
|
||||
interceptor.level = if (shouldLog)
|
||||
interceptor.level = if (shouldLog) {
|
||||
HttpLoggingInterceptor.Level.BODY
|
||||
else
|
||||
} else {
|
||||
HttpLoggingInterceptor.Level.NONE
|
||||
}
|
||||
val client = OkHttpClient.Builder().addInterceptor(interceptor).build()
|
||||
|
||||
val gson = GsonBuilder()
|
||||
.setLenient()
|
||||
.create()
|
||||
.setLenient()
|
||||
.create()
|
||||
val retrofit =
|
||||
Retrofit
|
||||
.Builder()
|
||||
.baseUrl("https://mercury.postlight.com")
|
||||
.baseUrl("https://www.amine-bou.fr")
|
||||
.client(client)
|
||||
.addConverterFactory(GsonConverterFactory.create(gson))
|
||||
.build()
|
||||
@@ -35,6 +34,6 @@ class MercuryApi(private val key: String, shouldLog: Boolean) {
|
||||
}
|
||||
|
||||
fun parseUrl(url: String): Call<ParsedContent> {
|
||||
return service.parseUrl(url, this.key)
|
||||
return service.parseUrl(url)
|
||||
}
|
||||
}
|
||||
@@ -4,24 +4,27 @@ import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
|
||||
class ParsedContent(@SerializedName("title") val title: String,
|
||||
@SerializedName("content") val content: String,
|
||||
@SerializedName("date_published") val date_published: String,
|
||||
@SerializedName("lead_image_url") val lead_image_url: String,
|
||||
@SerializedName("dek") val dek: String,
|
||||
@SerializedName("url") val url: String,
|
||||
@SerializedName("domain") val domain: String,
|
||||
@SerializedName("excerpt") val excerpt: String,
|
||||
@SerializedName("total_pages") val total_pages: Int,
|
||||
@SerializedName("rendered_pages") val rendered_pages: Int,
|
||||
@SerializedName("next_page_url") val next_page_url: String) : Parcelable {
|
||||
class ParsedContent(
|
||||
@SerializedName("title") val title: String,
|
||||
@SerializedName("content") val content: String?,
|
||||
@SerializedName("date_published") val date_published: String,
|
||||
@SerializedName("lead_image_url") val lead_image_url: String?,
|
||||
@SerializedName("dek") val dek: String,
|
||||
@SerializedName("url") val url: String,
|
||||
@SerializedName("domain") val domain: String,
|
||||
@SerializedName("excerpt") val excerpt: String,
|
||||
@SerializedName("total_pages") val total_pages: Int,
|
||||
@SerializedName("rendered_pages") val rendered_pages: Int,
|
||||
@SerializedName("next_page_url") val next_page_url: String
|
||||
) : Parcelable {
|
||||
|
||||
companion object {
|
||||
@JvmField val CREATOR: Parcelable.Creator<ParsedContent> = object : Parcelable.Creator<ParsedContent> {
|
||||
override fun createFromParcel(source: Parcel): ParsedContent = ParsedContent(source)
|
||||
override fun newArray(size: Int): Array<ParsedContent?> = arrayOfNulls(size)
|
||||
}
|
||||
@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(
|
||||
|
||||