Compare commits
268 Commits
v1.5.7
...
v171811310
Author | SHA1 | Date | |
---|---|---|---|
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 |
17
.github/CONTRIBUTING.md
vendored
@ -8,7 +8,7 @@ Please read the guidelines before contributing, and follow them (or try to) when
|
|||||||
|
|
||||||
### What you can do to help.
|
### 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)
|
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
|
### 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.
|
* Please ask before starting to work on an issue. I may be working on it, or someone else could be doing so.
|
||||||
* Each pull request should implement **ONE** feature or bugfix. Keep in mind that you can submit as many PR as you want.
|
* Each pull request should implement **ONE** feature or bugfix. Keep in mind that you can submit as many PR as you want.
|
||||||
* Your code must be simple and clear enough to avoid using comments to explain what it does.
|
* Your code must be simple and clear enough to avoid using comments to explain what it does.
|
||||||
@ -46,15 +47,8 @@ You can directly import this project into IntellIJ/Android Studio.
|
|||||||
|
|
||||||
You'll have to:
|
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)
|
- 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.**
|
- 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:
|
### Examples:
|
||||||
@ -64,15 +58,10 @@ You'll have to:
|
|||||||
appLoginUrl="URL" # It can be empty.
|
appLoginUrl="URL" # It can be empty.
|
||||||
appLoginUsername="LOGIN" # It can be empty.
|
appLoginUsername="LOGIN" # It can be empty.
|
||||||
appLoginPassword="PASS" # 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
|
#### 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"
|
||||||
```
|
```
|
||||||
|
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -5,6 +5,7 @@
|
|||||||
- [ ] I have updated the documentation accordingly.
|
- [ ] I have updated the documentation accordingly.
|
||||||
- [ ] I have added tests to cover my changes.
|
- [ ] I have added tests to cover my changes.
|
||||||
- [ ] All new and existing tests passed.
|
- [ ] 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
|
This closes issue #XXX
|
||||||
|
|
||||||
|
62
CHANGELOG.md
@ -1,8 +1,66 @@
|
|||||||
|
**1.7.x**
|
||||||
|
|
||||||
|
- 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**
|
**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)
|
- Added an option to use a webview in the article viewer (see #149)
|
||||||
|
|
||||||
- Fixes (#151)
|
- 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**
|
**1.5.5.x (didn't last long) AND 1.5.6.x**
|
||||||
|
|
||||||
@ -20,6 +78,8 @@
|
|||||||
|
|
||||||
- Changed versions handling.
|
- Changed versions handling.
|
||||||
|
|
||||||
|
- Removed indonesian english as it was causing issues with the english version of the app.
|
||||||
|
|
||||||
**1.5.4.22**
|
**1.5.4.22**
|
||||||
|
|
||||||
- You can now scroll through the loaded articles !
|
- You can now scroll through the loaded articles !
|
||||||
|
21
README.md
@ -1,17 +1,20 @@
|
|||||||
# ReaderForSelfoss
|
# ReaderForSelfoss
|
||||||
|
|
||||||
[](https://crowdin.com/project/readerforselfoss) [](https://gitter.im/amine-bou/ReaderForSelfoss)
|
[](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)
|
||||||
|
|
||||||
[](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).
|
|
||||||
|
|
||||||
It's an RSS Reader for Android, that **only** works with [Selfoss](https://selfoss.aditu.de/)
|
It's an RSS Reader for Android, that **only** works with [Selfoss](https://selfoss.aditu.de/)
|
||||||
|
|
||||||
The last APK built from source is available [here](https://jenkins.amine-bou.fr/job/ReaderForSelfoss/lastSuccessfulBuild/artifact/SignApksBuilder-out/selfoss-key/selfoss/app-githubConfig-release-unsigned.apk/app-githubConfig-release.apk).
|
<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 ?
|
## 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)
|
- [See what I'm doing](https://github.com/aminecmi/ReaderforSelfoss/projects/1)
|
||||||
- [Create an issue, or request a new feature](https://github.com/aminecmi/ReaderforSelfoss/issues)
|
- [Create an issue, or request a new feature](https://github.com/aminecmi/ReaderforSelfoss/issues)
|
||||||
- [Help translation the app](https://crowdin.com/project/readerforselfoss)
|
- [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)
|
||||||
|
183
app/build.gradle
@ -1,17 +1,12 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
repositories {
|
|
||||||
maven { url 'https://maven.fabric.io/public' }
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
classpath 'io.fabric.tools:gradle:1.+'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
configuration = [
|
configuration = [
|
||||||
buildDate: new Date()
|
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 gitVersion() {
|
||||||
@ -20,40 +15,34 @@ def gitVersion() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def versionCodeFromGit() {
|
def versionCodeFromGit() {
|
||||||
def versionCode = gitVersion() + (ext.configuration.buildDate.format("yyMMD")).toInteger()
|
println "version code " + gitVersion()
|
||||||
println "version code " + versionCode
|
return gitVersion().toInteger()
|
||||||
return versionCode.toInteger()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def versionNameFromGit() {
|
def versionNameFromGit() {
|
||||||
def versionName = gitVersion() + ext.configuration.buildDate.format('yyyyMMddHHmm')
|
println "version name " + gitVersion()
|
||||||
println "version name " + versionName
|
return gitVersion()
|
||||||
return versionName
|
|
||||||
}
|
}
|
||||||
|
|
||||||
apply plugin: 'org.sonarqube'
|
apply plugin: 'kotlin-kapt'
|
||||||
|
|
||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
apply plugin: 'io.fabric'
|
|
||||||
|
|
||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
|
|
||||||
apply plugin: 'kotlin-android-extensions'
|
apply plugin: 'kotlin-android-extensions'
|
||||||
|
|
||||||
repositories {
|
|
||||||
maven {
|
|
||||||
url 'https://maven.fabric.io/public'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 27
|
compileOptions {
|
||||||
buildToolsVersion '27.0.2'
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
compileSdkVersion 28
|
||||||
|
buildToolsVersion '28.0.3'
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "apps.amine.bou.readerforselfoss"
|
applicationId "apps.amine.bou.readerforselfoss"
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 27
|
targetSdkVersion 28
|
||||||
versionCode versionCodeFromGit()
|
versionCode versionCodeFromGit()
|
||||||
versionName versionNameFromGit()
|
versionName versionNameFromGit()
|
||||||
|
|
||||||
@ -66,14 +55,14 @@ android {
|
|||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
|
|
||||||
// tests
|
// tests
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
buildConfigField "String", "MERCURY_KEY", mercuryApiKey
|
javaCompileOptions {
|
||||||
buildConfigField "String", "FEEDBACK_EMAIL", feedbackEmail
|
annotationProcessorOptions {
|
||||||
buildConfigField "String", "SOURCE_URL", sourceUrl
|
arguments = ["room.schemaLocation":
|
||||||
buildConfigField "String", "TRACKER_URL", trackerUrl
|
"$projectDir/schemas".toString()]
|
||||||
buildConfigField "String", "TRANSLATION_URL", translationUrl
|
}
|
||||||
buildConfigField "String", "GITHUB_TOKEN", githubToken
|
}
|
||||||
}
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
@ -86,6 +75,7 @@ android {
|
|||||||
buildConfigField "String", "LOGIN_URL", appLoginUrl
|
buildConfigField "String", "LOGIN_URL", appLoginUrl
|
||||||
buildConfigField "String", "LOGIN_USERNAME", appLoginUsername
|
buildConfigField "String", "LOGIN_USERNAME", appLoginUsername
|
||||||
buildConfigField "String", "LOGIN_PASSWORD", appLoginPassword
|
buildConfigField "String", "LOGIN_PASSWORD", appLoginPassword
|
||||||
|
applicationIdSuffix ".dev"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
flavorDimensions "build"
|
flavorDimensions "build"
|
||||||
@ -93,112 +83,90 @@ android {
|
|||||||
githubConfig {
|
githubConfig {
|
||||||
versionNameSuffix '-github'
|
versionNameSuffix '-github'
|
||||||
dimension "build"
|
dimension "build"
|
||||||
buildConfigField "boolean", "GITHUB_VERSION", "true"
|
|
||||||
}
|
}
|
||||||
storeConfig {
|
storeConfig {
|
||||||
|
// As jenkins publishes to alpha first, this is the default suffix now.
|
||||||
versionNameSuffix '-store'
|
versionNameSuffix '-store'
|
||||||
dimension "build"
|
dimension "build"
|
||||||
buildConfigField "boolean", "GITHUB_VERSION", "false"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// Testing
|
// Testing
|
||||||
androidTestCompile 'com.android.support.test.espresso:espresso-core:3.0.1'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0-beta02'
|
||||||
androidTestCompile 'com.android.support.test:runner:1.0.1'
|
androidTestImplementation 'androidx.test:runner:1.1.0-beta02'
|
||||||
// Espresso-contrib for DatePicker, RecyclerView, Drawer actions, Accessibility checks, CountingIdlingResource
|
// Espresso-contrib for DatePicker, RecyclerView, Drawer actions, Accessibility checks, CountingIdlingResource
|
||||||
androidTestCompile 'com.android.support.test.espresso:espresso-contrib:3.0.1'
|
androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.1.0-beta02'
|
||||||
// Espresso-intents for validation and stubbing of Intents
|
// Espresso-intents for validation and stubbing of Intents
|
||||||
androidTestCompile 'com.android.support.test.espresso:espresso-intents:3.0.1'
|
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"
|
||||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
|
||||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
|
|
||||||
|
|
||||||
// Android Support
|
// Android Support
|
||||||
compile 'com.android.support:appcompat-v7:27.0.2'
|
implementation "androidx.appcompat:appcompat:$android_version"
|
||||||
compile 'com.android.support:design:27.0.2'
|
implementation "com.google.android.material:material:$android_version"
|
||||||
compile 'com.android.support:recyclerview-v7:27.0.2'
|
implementation "androidx.recyclerview:recyclerview:$android_version"
|
||||||
compile 'com.android.support:support-v4:27.0.2'
|
implementation "androidx.legacy:legacy-support-v4:$android_version"
|
||||||
compile 'com.android.support:support-vector-drawable:27.0.2'
|
implementation "androidx.vectordrawable:vectordrawable:$android_version"
|
||||||
compile 'com.android.support:customtabs:27.0.2'
|
implementation "androidx.browser:browser:$android_version"
|
||||||
compile 'com.android.support:cardview-v7:27.0.2'
|
implementation "androidx.cardview:cardview:$android_version"
|
||||||
compile 'com.android.support.constraint:constraint-layout:1.0.2'
|
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha2'
|
||||||
|
|
||||||
// Firebase + crashlytics
|
|
||||||
compile 'com.google.firebase:firebase-core:11.6.2'
|
|
||||||
compile 'com.google.firebase:firebase-config:11.6.2'
|
|
||||||
compile 'com.google.firebase:firebase-invites:11.6.2'
|
|
||||||
compile('com.crashlytics.sdk.android:crashlytics:2.8.0@aar') {
|
|
||||||
transitive = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//multidex
|
//multidex
|
||||||
compile 'com.android.support:multidex:1.0.2'
|
implementation 'androidx.multidex:multidex:2.0.0'
|
||||||
|
|
||||||
// Intro
|
|
||||||
compile 'agency.tango.android:material-intro-screen:0.0.5'
|
|
||||||
|
|
||||||
// About
|
// About
|
||||||
compile('com.mikepenz:aboutlibraries:6.0.0@aar') {
|
implementation('com.mikepenz:aboutlibraries:6.2.0@aar') {
|
||||||
transitive = true
|
transitive = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrofit + http logging + okhttp
|
// Retrofit + http logging + okhttp
|
||||||
compile 'com.squareup.retrofit2:retrofit:2.3.0'
|
implementation 'com.squareup.retrofit2:retrofit:2.3.0'
|
||||||
compile 'com.squareup.okhttp3:logging-interceptor:3.9.0'
|
implementation 'com.squareup.okhttp3:logging-interceptor:3.9.0'
|
||||||
compile 'com.squareup.retrofit2:converter-gson:2.3.0'
|
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
|
||||||
compile 'com.burgstaller:okhttp-digest:1.12'
|
implementation 'com.burgstaller:okhttp-digest:1.12'
|
||||||
|
|
||||||
// Material-ish things
|
// Material-ish things
|
||||||
compile 'com.ashokvarma.android:bottom-navigation-bar:2.0.3'
|
implementation 'com.ashokvarma.android:bottom-navigation-bar:2.0.5'
|
||||||
compile 'com.github.jd-alexander:LikeButton:0.2.1'
|
implementation 'com.github.jd-alexander:LikeButton:0.2.3'
|
||||||
compile 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
|
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
|
||||||
compile 'org.sufficientlysecure:html-textview:3.5'
|
|
||||||
|
|
||||||
// glide
|
// glide
|
||||||
compile 'com.github.bumptech.glide:glide:4.1.1'
|
implementation 'com.github.bumptech.glide:glide:4.1.1'
|
||||||
compile 'com.github.bumptech.glide:okhttp3-integration:4.1.1'
|
implementation 'com.github.bumptech.glide:okhttp3-integration:4.1.1'
|
||||||
|
|
||||||
// Asking politely users to rate the app
|
// Asking politely users to rate the app
|
||||||
compile 'com.github.stkent:amplify:2.1.0'
|
implementation 'com.github.stkent:amplify:2.2.0'
|
||||||
|
|
||||||
// Drawer
|
// Drawer
|
||||||
implementation 'co.zsmb:materialdrawer-kt:1.2.1'
|
implementation 'co.zsmb:materialdrawer-kt:2.0.1'
|
||||||
compile 'com.anupcowkur:reservoir:3.1.0'
|
|
||||||
|
|
||||||
// Themes
|
// Themes
|
||||||
compile 'com.52inc:scoops:1.0.0'
|
implementation 'com.52inc:scoops:1.0.0'
|
||||||
|
implementation 'com.jaredrummler:colorpicker:1.0.2'
|
||||||
// Github issues reporter
|
implementation 'com.github.rubensousa:floatingtoolbar:1.5.1'
|
||||||
compile 'com.heinrichreimersoftware:android-issue-reporter:1.3.1'
|
|
||||||
|
|
||||||
compile 'com.github.rubensousa:floatingtoolbar:1.5.1'
|
|
||||||
|
|
||||||
// Pager
|
// Pager
|
||||||
compile 'me.relex:circleindicator:1.2.2@aar'
|
implementation 'me.relex:circleindicator:2.0.0@aar'
|
||||||
|
|
||||||
|
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 {
|
afterEvaluate {
|
||||||
initFabricPropertiesIfNeeded()
|
|
||||||
initAppLoginPropertiesIfNeeded()
|
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() {
|
def initAppLoginPropertiesIfNeeded() {
|
||||||
@ -211,19 +179,4 @@ def initAppLoginPropertiesIfNeeded() {
|
|||||||
entry(key: "appLoginPassword", value: System.getProperty("appLoginPassword"))
|
entry(key: "appLoginPassword", value: System.getProperty("appLoginPassword"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def initAppForSecretPropertiesIfNeeded() {
|
|
||||||
def propertiesFile = file(System.getProperty("user.home") + '/.gradle/gradle.properties')
|
|
||||||
if (!propertiesFile.exists()) {
|
|
||||||
def commentMessage = "This is autogenerated local property from system environment to prevent key to be committed to source control."
|
|
||||||
ant.propertyfile(file: System.getProperty("user.home") + "/.gradle/gradle.properties", comment: commentMessage) {
|
|
||||||
entry(key: "mercuryApiKey", value: System.getProperty("mercuryApiKey"))
|
|
||||||
entry(key: "feedbackEmail", value: System.getProperty("feedbackEmail"))
|
|
||||||
entry(key: "sourceUrl", value: System.getProperty("sourceUrl"))
|
|
||||||
entry(key: "trackerUrl", value: System.getProperty("trackerUrl"))
|
|
||||||
entry(key: "translationUrl", value: System.getProperty("translationUrl"))
|
|
||||||
entry(key: "githubToken", value: System.getProperty("githubToken"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
27
app/proguard-rules.pro
vendored
@ -30,25 +30,13 @@
|
|||||||
<fields>;
|
<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 okio.**
|
||||||
-dontwarn retrofit2.Platform$Java8
|
-dontwarn retrofit2.Platform$Java8
|
||||||
-keepattributes Signature
|
-keep class retrofit.** { *; }
|
||||||
|
-keepclasseswithmembers class * {
|
||||||
|
@retrofit.http.* <methods>;
|
||||||
|
}
|
||||||
|
-keepattributes *Annotation*,Signature
|
||||||
-keepattributes Exceptions
|
-keepattributes Exceptions
|
||||||
-dontwarn okio.**
|
-dontwarn okio.**
|
||||||
-dontwarn javax.annotation.Nullable
|
-dontwarn javax.annotation.Nullable
|
||||||
@ -71,4 +59,7 @@
|
|||||||
|
|
||||||
-dontwarn javax.annotation.**
|
-dontwarn javax.annotation.**
|
||||||
|
|
||||||
-keep class android.support.v7.widget.SearchView { *; }
|
-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\")"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
@ -2,30 +2,29 @@ package apps.amine.bou.readerforselfoss
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.support.test.InstrumentationRegistry
|
import androidx.test.InstrumentationRegistry
|
||||||
import android.support.test.espresso.Espresso.onView
|
import androidx.test.espresso.Espresso.onView
|
||||||
import android.support.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
|
import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
|
||||||
import android.support.test.espresso.action.ViewActions.click
|
import androidx.test.espresso.action.ViewActions.click
|
||||||
import android.support.test.espresso.action.ViewActions.closeSoftKeyboard
|
import androidx.test.espresso.action.ViewActions.closeSoftKeyboard
|
||||||
import android.support.test.espresso.action.ViewActions.pressBack
|
import androidx.test.espresso.action.ViewActions.pressBack
|
||||||
import android.support.test.espresso.action.ViewActions.pressKey
|
import androidx.test.espresso.action.ViewActions.pressKey
|
||||||
import android.support.test.espresso.action.ViewActions.typeText
|
import androidx.test.espresso.action.ViewActions.typeText
|
||||||
import android.support.test.espresso.assertion.ViewAssertions.matches
|
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||||
import android.support.test.espresso.contrib.DrawerActions
|
import androidx.test.espresso.contrib.DrawerActions
|
||||||
import android.support.test.espresso.intent.Intents
|
import androidx.test.espresso.intent.Intents
|
||||||
import android.support.test.espresso.intent.Intents.intended
|
import androidx.test.espresso.intent.Intents.intended
|
||||||
import android.support.test.espresso.intent.Intents.times
|
import androidx.test.espresso.intent.Intents.times
|
||||||
import android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent
|
import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
|
||||||
import android.support.test.espresso.matcher.ViewMatchers.isDisplayed
|
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||||
import android.support.test.espresso.matcher.ViewMatchers.isRoot
|
import androidx.test.espresso.matcher.ViewMatchers.isRoot
|
||||||
import android.support.test.espresso.matcher.ViewMatchers.withContentDescription
|
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
|
||||||
import android.support.test.espresso.matcher.ViewMatchers.withId
|
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||||
import android.support.test.espresso.matcher.ViewMatchers.withText
|
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||||
import android.support.test.rule.ActivityTestRule
|
import androidx.test.rule.ActivityTestRule
|
||||||
import android.support.test.runner.AndroidJUnit4
|
import androidx.test.runner.AndroidJUnit4
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import apps.amine.bou.readerforselfoss.utils.Config
|
import apps.amine.bou.readerforselfoss.utils.Config
|
||||||
import com.heinrichreimersoftware.androidissuereporter.IssueReporterLauncher
|
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
@ -92,25 +91,6 @@ class HomeActivityEspressoTest {
|
|||||||
intended(hasComponent(LoginActivity::class.java.name), times(1))
|
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
|
// TODO: test articles opening and actions for cards and lists
|
||||||
|
|
||||||
@After
|
@After
|
||||||
|
@ -1,91 +0,0 @@
|
|||||||
package apps.amine.bou.readerforselfoss
|
|
||||||
|
|
||||||
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.hasComponent
|
|
||||||
import android.support.test.espresso.matcher.ViewMatchers.isDisplayed
|
|
||||||
import android.support.test.espresso.matcher.ViewMatchers.withId
|
|
||||||
import android.support.test.espresso.matcher.ViewMatchers.withText
|
|
||||||
import android.support.test.rule.ActivityTestRule
|
|
||||||
import android.support.test.runner.AndroidJUnit4
|
|
||||||
import 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 java.util.*
|
|
||||||
|
|
||||||
@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()
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,25 +2,25 @@ package apps.amine.bou.readerforselfoss
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.support.test.InstrumentationRegistry
|
import androidx.test.InstrumentationRegistry
|
||||||
import android.support.test.espresso.Espresso.onView
|
import androidx.test.espresso.Espresso.onView
|
||||||
import android.support.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
|
import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
|
||||||
import android.support.test.espresso.action.ViewActions.click
|
import androidx.test.espresso.action.ViewActions.click
|
||||||
import android.support.test.espresso.action.ViewActions.closeSoftKeyboard
|
import androidx.test.espresso.action.ViewActions.closeSoftKeyboard
|
||||||
import android.support.test.espresso.action.ViewActions.pressBack
|
import androidx.test.espresso.action.ViewActions.pressBack
|
||||||
import android.support.test.espresso.action.ViewActions.typeText
|
import androidx.test.espresso.action.ViewActions.typeText
|
||||||
import android.support.test.espresso.assertion.ViewAssertions.matches
|
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||||
import android.support.test.espresso.intent.Intents
|
import androidx.test.espresso.intent.Intents
|
||||||
import android.support.test.espresso.intent.Intents.intended
|
import androidx.test.espresso.intent.Intents.intended
|
||||||
import android.support.test.espresso.intent.Intents.times
|
import androidx.test.espresso.intent.Intents.times
|
||||||
import android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent
|
import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
|
||||||
import android.support.test.espresso.matcher.ViewMatchers
|
import androidx.test.espresso.matcher.ViewMatchers
|
||||||
import android.support.test.espresso.matcher.ViewMatchers.isRoot
|
import androidx.test.espresso.matcher.ViewMatchers.isRoot
|
||||||
import android.support.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
|
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
|
||||||
import android.support.test.espresso.matcher.ViewMatchers.withId
|
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||||
import android.support.test.espresso.matcher.ViewMatchers.withText
|
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||||
import android.support.test.rule.ActivityTestRule
|
import androidx.test.rule.ActivityTestRule
|
||||||
import android.support.test.runner.AndroidJUnit4
|
import androidx.test.runner.AndroidJUnit4
|
||||||
import apps.amine.bou.readerforselfoss.utils.Config
|
import apps.amine.bou.readerforselfoss.utils.Config
|
||||||
import com.mikepenz.aboutlibraries.ui.LibsActivity
|
import com.mikepenz.aboutlibraries.ui.LibsActivity
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
|
@ -3,13 +3,13 @@ package apps.amine.bou.readerforselfoss
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.preference.PreferenceManager
|
import android.preference.PreferenceManager
|
||||||
import android.support.test.InstrumentationRegistry.getInstrumentation
|
import androidx.test.InstrumentationRegistry.getInstrumentation
|
||||||
import android.support.test.espresso.intent.Intents
|
import androidx.test.espresso.intent.Intents
|
||||||
import android.support.test.espresso.intent.Intents.intended
|
import androidx.test.espresso.intent.Intents.intended
|
||||||
import android.support.test.espresso.intent.Intents.times
|
import androidx.test.espresso.intent.Intents.times
|
||||||
import android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent
|
import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
|
||||||
import android.support.test.rule.ActivityTestRule
|
import androidx.test.rule.ActivityTestRule
|
||||||
import android.support.test.runner.AndroidJUnit4
|
import androidx.test.runner.AndroidJUnit4
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
|
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
@ -45,7 +45,6 @@ class MainActivityEspressoTest {
|
|||||||
rule.launchActivity(intent)
|
rule.launchActivity(intent)
|
||||||
|
|
||||||
intended(hasComponent(MainActivity::class.java.name))
|
intended(hasComponent(MainActivity::class.java.name))
|
||||||
intended(hasComponent(IntroActivity::class.java.name))
|
|
||||||
intended(hasComponent(LoginActivity::class.java.name), times(0))
|
intended(hasComponent(LoginActivity::class.java.name), times(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,7 +57,6 @@ class MainActivityEspressoTest {
|
|||||||
|
|
||||||
intended(hasComponent(MainActivity::class.java.name))
|
intended(hasComponent(MainActivity::class.java.name))
|
||||||
intended(hasComponent(LoginActivity::class.java.name))
|
intended(hasComponent(LoginActivity::class.java.name))
|
||||||
intended(hasComponent(IntroActivity::class.java.name), times(0))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package apps.amine.bou.readerforselfoss
|
package apps.amine.bou.readerforselfoss
|
||||||
|
|
||||||
import android.support.design.widget.TextInputLayout
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
import android.support.test.espresso.matcher.ViewMatchers
|
import androidx.test.espresso.matcher.ViewMatchers
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import org.hamcrest.Description
|
import org.hamcrest.Description
|
||||||
import org.hamcrest.Matcher
|
import org.hamcrest.Matcher
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<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.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".MyApp"
|
android:name=".MyApp"
|
||||||
@ -24,10 +22,6 @@
|
|||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
|
||||||
android:name=".IntroActivity"
|
|
||||||
android:theme="@style/Theme.Intro">
|
|
||||||
</activity>
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".LoginActivity"
|
android:name=".LoginActivity"
|
||||||
android:label="@string/title_activity_login">
|
android:label="@string/title_activity_login">
|
||||||
@ -77,6 +71,8 @@
|
|||||||
|
|
||||||
<meta-data android:name="android.webkit.WebView.EnableSafeBrowsing"
|
<meta-data android:name="android.webkit.WebView.EnableSafeBrowsing"
|
||||||
android:value="true" />
|
android:value="true" />
|
||||||
|
|
||||||
|
<meta-data android:name="android.max_aspect" android:value="2.1" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
package apps.amine.bou.readerforselfoss
|
package apps.amine.bou.readerforselfoss
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.preference.PreferenceManager
|
import android.preference.PreferenceManager
|
||||||
import android.support.constraint.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import android.support.v7.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.AdapterView
|
import android.widget.AdapterView
|
||||||
import android.widget.ArrayAdapter
|
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.SelfossApi
|
||||||
import apps.amine.bou.readerforselfoss.api.selfoss.Spout
|
import apps.amine.bou.readerforselfoss.api.selfoss.Spout
|
||||||
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
|
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
|
||||||
|
import apps.amine.bou.readerforselfoss.themes.AppColors
|
||||||
|
import apps.amine.bou.readerforselfoss.themes.Toppings
|
||||||
import apps.amine.bou.readerforselfoss.utils.Config
|
import apps.amine.bou.readerforselfoss.utils.Config
|
||||||
import apps.amine.bou.readerforselfoss.utils.isBaseUrlValid
|
import apps.amine.bou.readerforselfoss.utils.isBaseUrlValid
|
||||||
import com.ftinc.scoop.Scoop
|
import com.ftinc.scoop.Scoop
|
||||||
@ -23,29 +26,70 @@ import kotlinx.android.synthetic.main.activity_add_source.*
|
|||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
import retrofit2.Callback
|
import retrofit2.Callback
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
|
import android.graphics.PorterDuff
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class AddSourceActivity : AppCompatActivity() {
|
class AddSourceActivity : AppCompatActivity() {
|
||||||
|
|
||||||
private var mSpoutsValue: String? = null
|
private var mSpoutsValue: String? = null
|
||||||
|
private lateinit var api: SelfossApi
|
||||||
|
|
||||||
|
private lateinit var appColors: AppColors
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
appColors = AppColors(this@AddSourceActivity)
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
Scoop.getInstance().apply(this)
|
|
||||||
setContentView(R.layout.activity_add_source)
|
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)
|
setSupportActionBar(toolbar)
|
||||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
supportActionBar?.setDisplayShowHomeEnabled(true)
|
supportActionBar?.setDisplayShowHomeEnabled(true)
|
||||||
|
|
||||||
var api: SelfossApi? = null
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
|
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
api = SelfossApi(
|
api = SelfossApi(
|
||||||
this,
|
this,
|
||||||
this@AddSourceActivity,
|
this@AddSourceActivity,
|
||||||
prefs.getBoolean("isSelfSignedCert", false),
|
prefs.getBoolean("isSelfSignedCert", false),
|
||||||
prefs.getBoolean("should_log_everything", false)
|
prefs.getBoolean("should_log_everything", false)
|
||||||
)
|
)
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
mustLoginToAddSource()
|
mustLoginToAddSource()
|
||||||
@ -53,10 +97,15 @@ class AddSourceActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
maybeGetDetailsFromIntentSharing(intent, sourceUri, nameInput)
|
maybeGetDetailsFromIntentSharing(intent, sourceUri, nameInput)
|
||||||
|
|
||||||
|
saveBtn.setTextColor(appColors.colorAccent)
|
||||||
|
|
||||||
saveBtn.setOnClickListener {
|
saveBtn.setOnClickListener {
|
||||||
handleSaveSource(tags, nameInput.text.toString(), sourceUri.text.toString(), api!!)
|
handleSaveSource(tags, nameInput.text.toString(), sourceUri.text.toString(), api!!)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
val config = Config(this)
|
val config = Config(this)
|
||||||
|
|
||||||
if (config.baseUrl.isEmpty() || !config.baseUrl.isBaseUrlValid()) {
|
if (config.baseUrl.isEmpty() || !config.baseUrl.isBaseUrlValid()) {
|
||||||
@ -67,16 +116,18 @@ class AddSourceActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handleSpoutsSpinner(
|
private fun handleSpoutsSpinner(
|
||||||
spoutsSpinner: Spinner,
|
spoutsSpinner: Spinner,
|
||||||
api: SelfossApi?,
|
api: SelfossApi?,
|
||||||
mProgress: ProgressBar,
|
mProgress: ProgressBar,
|
||||||
formContainer: ConstraintLayout
|
formContainer: ConstraintLayout
|
||||||
) {
|
) {
|
||||||
val spoutsKV = HashMap<String, String>()
|
val spoutsKV = HashMap<String, String>()
|
||||||
spoutsSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
spoutsSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||||
override fun onItemSelected(adapterView: AdapterView<*>, view: View, i: Int, l: Long) {
|
override fun onItemSelected(adapterView: AdapterView<*>, view: View?, i: Int, l: Long) {
|
||||||
val spoutName = (view as TextView).text.toString()
|
if (view != null) {
|
||||||
mSpoutsValue = spoutsKV[spoutName]
|
val spoutName = (view as TextView).text.toString()
|
||||||
|
mSpoutsValue = spoutsKV[spoutName]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNothingSelected(adapterView: AdapterView<*>) {
|
override fun onNothingSelected(adapterView: AdapterView<*>) {
|
||||||
@ -87,26 +138,26 @@ class AddSourceActivity : AppCompatActivity() {
|
|||||||
var items: Map<String, Spout>
|
var items: Map<String, Spout>
|
||||||
api!!.spouts().enqueue(object : Callback<Map<String, Spout>> {
|
api!!.spouts().enqueue(object : Callback<Map<String, Spout>> {
|
||||||
override fun onResponse(
|
override fun onResponse(
|
||||||
call: Call<Map<String, Spout>>,
|
call: Call<Map<String, Spout>>,
|
||||||
response: Response<Map<String, Spout>>
|
response: Response<Map<String, Spout>>
|
||||||
) {
|
) {
|
||||||
if (response.body() != null) {
|
if (response.body() != null) {
|
||||||
items = response.body()!!
|
items = response.body()!!
|
||||||
|
|
||||||
val itemsStrings = items.map { it.value.name }
|
val itemsStrings = items.map { it.value.name }
|
||||||
for ((key, value) in items) {
|
for ((key, value) in items) {
|
||||||
spoutsKV.put(value.name, key)
|
spoutsKV[value.name] = key
|
||||||
}
|
}
|
||||||
|
|
||||||
mProgress.visibility = View.GONE
|
mProgress.visibility = View.GONE
|
||||||
formContainer.visibility = View.VISIBLE
|
formContainer.visibility = View.VISIBLE
|
||||||
|
|
||||||
val spinnerArrayAdapter =
|
val spinnerArrayAdapter =
|
||||||
ArrayAdapter(
|
ArrayAdapter(
|
||||||
this@AddSourceActivity,
|
this@AddSourceActivity,
|
||||||
android.R.layout.simple_spinner_item,
|
android.R.layout.simple_spinner_item,
|
||||||
itemsStrings
|
itemsStrings
|
||||||
)
|
)
|
||||||
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||||
spoutsSpinner.adapter = spinnerArrayAdapter
|
spoutsSpinner.adapter = spinnerArrayAdapter
|
||||||
} else {
|
} else {
|
||||||
@ -120,9 +171,9 @@ class AddSourceActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
private fun handleProblemWithSpouts() {
|
private fun handleProblemWithSpouts() {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
this@AddSourceActivity,
|
this@AddSourceActivity,
|
||||||
R.string.cant_get_spouts,
|
R.string.cant_get_spouts,
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
mProgress.visibility = View.GONE
|
mProgress.visibility = View.GONE
|
||||||
}
|
}
|
||||||
@ -130,9 +181,9 @@ class AddSourceActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun maybeGetDetailsFromIntentSharing(
|
private fun maybeGetDetailsFromIntentSharing(
|
||||||
intent: Intent,
|
intent: Intent,
|
||||||
sourceUri: EditText,
|
sourceUri: EditText,
|
||||||
nameInput: EditText
|
nameInput: EditText
|
||||||
) {
|
) {
|
||||||
if (Intent.ACTION_SEND == intent.action && "text/plain" == intent.type) {
|
if (Intent.ACTION_SEND == intent.action && "text/plain" == intent.type) {
|
||||||
sourceUri.setText(intent.getStringExtra(Intent.EXTRA_TEXT))
|
sourceUri.setText(intent.getStringExtra(Intent.EXTRA_TEXT))
|
||||||
@ -149,38 +200,39 @@ class AddSourceActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
private fun handleSaveSource(tags: EditText, title: String, url: String, api: SelfossApi) {
|
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) {
|
if (sourceDetailsAvailable) {
|
||||||
Toast.makeText(this, R.string.form_not_complete, Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, R.string.form_not_complete, Toast.LENGTH_SHORT).show()
|
||||||
} else {
|
} else {
|
||||||
api.createSource(
|
api.createSource(
|
||||||
title,
|
title,
|
||||||
url,
|
url,
|
||||||
mSpoutsValue!!,
|
mSpoutsValue!!,
|
||||||
tags.text.toString(),
|
tags.text.toString(),
|
||||||
""
|
""
|
||||||
).enqueue(object : Callback<SuccessResponse> {
|
).enqueue(object : Callback<SuccessResponse> {
|
||||||
override fun onResponse(
|
override fun onResponse(
|
||||||
call: Call<SuccessResponse>,
|
call: Call<SuccessResponse>,
|
||||||
response: Response<SuccessResponse>
|
response: Response<SuccessResponse>
|
||||||
) {
|
) {
|
||||||
if (response.body() != null && response.body()!!.isSuccess) {
|
if (response.body() != null && response.body()!!.isSuccess) {
|
||||||
finish()
|
finish()
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
this@AddSourceActivity,
|
this@AddSourceActivity,
|
||||||
R.string.cant_create_source,
|
R.string.cant_create_source,
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
|
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
this@AddSourceActivity,
|
this@AddSourceActivity,
|
||||||
R.string.cant_create_source,
|
R.string.cant_create_source,
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -1,70 +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,8 +6,8 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v7.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import android.support.v7.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
@ -17,14 +17,15 @@ import android.widget.TextView
|
|||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
|
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
|
||||||
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
|
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.Config
|
||||||
import apps.amine.bou.readerforselfoss.utils.isBaseUrlValid
|
import apps.amine.bou.readerforselfoss.utils.isBaseUrlValid
|
||||||
import com.crashlytics.android.Crashlytics
|
import apps.amine.bou.readerforselfoss.utils.maybeHandleSilentException
|
||||||
import com.ftinc.scoop.Scoop
|
import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible
|
||||||
import com.google.firebase.analytics.FirebaseAnalytics
|
|
||||||
import com.mikepenz.aboutlibraries.Libs
|
import com.mikepenz.aboutlibraries.Libs
|
||||||
import com.mikepenz.aboutlibraries.LibsBuilder
|
import com.mikepenz.aboutlibraries.LibsBuilder
|
||||||
import kotlinx.android.synthetic.main.activity_login.*
|
import kotlinx.android.synthetic.main.activity_login.*
|
||||||
|
import org.acra.ACRA
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
import retrofit2.Callback
|
import retrofit2.Callback
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
@ -38,13 +39,15 @@ class LoginActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
private lateinit var settings: SharedPreferences
|
private lateinit var settings: SharedPreferences
|
||||||
private lateinit var editor: SharedPreferences.Editor
|
private lateinit var editor: SharedPreferences.Editor
|
||||||
private lateinit var firebaseAnalytics: FirebaseAnalytics
|
|
||||||
private lateinit var userIdentifier: String
|
private lateinit var userIdentifier: String
|
||||||
private var logErrors: Boolean = false
|
private var logErrors: Boolean = false
|
||||||
|
private lateinit var appColors: AppColors
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
appColors = AppColors(this@LoginActivity)
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
Scoop.getInstance().apply(this)
|
|
||||||
setContentView(R.layout.activity_login)
|
setContentView(R.layout.activity_login)
|
||||||
|
|
||||||
setSupportActionBar(toolbar)
|
setSupportActionBar(toolbar)
|
||||||
@ -62,8 +65,6 @@ class LoginActivity : AppCompatActivity() {
|
|||||||
goToMain()
|
goToMain()
|
||||||
}
|
}
|
||||||
|
|
||||||
firebaseAnalytics = FirebaseAnalytics.getInstance(this)
|
|
||||||
|
|
||||||
handleActions()
|
handleActions()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,13 +78,13 @@ class LoginActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
passwordView.setOnEditorActionListener(
|
passwordView.setOnEditorActionListener(
|
||||||
TextView.OnEditorActionListener { _, id, _ ->
|
TextView.OnEditorActionListener { _, id, _ ->
|
||||||
if (id == R.id.loginView || id == EditorInfo.IME_NULL) {
|
if (id == R.id.loginView || id == EditorInfo.IME_NULL) {
|
||||||
attemptLogin()
|
attemptLogin()
|
||||||
return@OnEditorActionListener true
|
return@OnEditorActionListener true
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
signInButton.setOnClickListener { attemptLogin() }
|
signInButton.setOnClickListener { attemptLogin() }
|
||||||
@ -111,9 +112,9 @@ class LoginActivity : AppCompatActivity() {
|
|||||||
alertDialog.setTitle(getString(R.string.warning_wrong_url))
|
alertDialog.setTitle(getString(R.string.warning_wrong_url))
|
||||||
alertDialog.setMessage(getString(R.string.base_url_error))
|
alertDialog.setMessage(getString(R.string.base_url_error))
|
||||||
alertDialog.setButton(
|
alertDialog.setButton(
|
||||||
AlertDialog.BUTTON_NEUTRAL,
|
AlertDialog.BUTTON_NEUTRAL,
|
||||||
"OK",
|
"OK",
|
||||||
{ dialog, _ -> dialog.dismiss() }
|
{ dialog, _ -> dialog.dismiss() }
|
||||||
)
|
)
|
||||||
alertDialog.show()
|
alertDialog.show()
|
||||||
}
|
}
|
||||||
@ -154,9 +155,9 @@ class LoginActivity : AppCompatActivity() {
|
|||||||
alertDialog.setTitle(getString(R.string.warning_wrong_url))
|
alertDialog.setTitle(getString(R.string.warning_wrong_url))
|
||||||
alertDialog.setMessage(getString(R.string.text_wrong_url))
|
alertDialog.setMessage(getString(R.string.text_wrong_url))
|
||||||
alertDialog.setButton(
|
alertDialog.setButton(
|
||||||
AlertDialog.BUTTON_NEUTRAL,
|
AlertDialog.BUTTON_NEUTRAL,
|
||||||
"OK",
|
"OK",
|
||||||
{ dialog, _ -> dialog.dismiss() }
|
{ dialog, _ -> dialog.dismiss() }
|
||||||
)
|
)
|
||||||
alertDialog.show()
|
alertDialog.show()
|
||||||
inValidCount = 0
|
inValidCount = 0
|
||||||
@ -191,53 +192,55 @@ class LoginActivity : AppCompatActivity() {
|
|||||||
editor.apply()
|
editor.apply()
|
||||||
|
|
||||||
val api = SelfossApi(
|
val api = SelfossApi(
|
||||||
this,
|
this,
|
||||||
this@LoginActivity,
|
this@LoginActivity,
|
||||||
isWithSelfSignedCert,
|
isWithSelfSignedCert,
|
||||||
isWithSelfSignedCert
|
isWithSelfSignedCert
|
||||||
)
|
)
|
||||||
api.login().enqueue(object : Callback<SuccessResponse> {
|
|
||||||
private fun preferenceError(t: Throwable) {
|
if (this@LoginActivity.isNetworkAccessible(this@LoginActivity.findViewById(R.id.loginForm))) {
|
||||||
editor.remove("url")
|
api.login().enqueue(object : Callback<SuccessResponse> {
|
||||||
editor.remove("login")
|
private fun preferenceError(t: Throwable) {
|
||||||
editor.remove("httpUserName")
|
editor.remove("url")
|
||||||
editor.remove("password")
|
editor.remove("login")
|
||||||
editor.remove("httpPassword")
|
editor.remove("httpUserName")
|
||||||
editor.apply()
|
editor.remove("password")
|
||||||
urlView.error = getString(R.string.wrong_infos)
|
editor.remove("httpPassword")
|
||||||
loginView.error = getString(R.string.wrong_infos)
|
editor.apply()
|
||||||
passwordView.error = getString(R.string.wrong_infos)
|
urlView.error = getString(R.string.wrong_infos)
|
||||||
httpLoginView.error = getString(R.string.wrong_infos)
|
loginView.error = getString(R.string.wrong_infos)
|
||||||
httpPasswordView.error = getString(R.string.wrong_infos)
|
passwordView.error = getString(R.string.wrong_infos)
|
||||||
if (logErrors) {
|
httpLoginView.error = getString(R.string.wrong_infos)
|
||||||
Crashlytics.setUserIdentifier(userIdentifier)
|
httpPasswordView.error = getString(R.string.wrong_infos)
|
||||||
Crashlytics.log(100, "LOGIN_DEBUG_ERRROR", t.message)
|
if (logErrors) {
|
||||||
Crashlytics.logException(t)
|
ACRA.getErrorReporter().maybeHandleSilentException(t, this@LoginActivity)
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
this@LoginActivity,
|
this@LoginActivity,
|
||||||
t.message,
|
t.message,
|
||||||
Toast.LENGTH_LONG
|
Toast.LENGTH_LONG
|
||||||
).show()
|
).show()
|
||||||
|
}
|
||||||
|
showProgress(false)
|
||||||
}
|
}
|
||||||
showProgress(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResponse(
|
override fun onResponse(
|
||||||
call: Call<SuccessResponse>,
|
call: Call<SuccessResponse>,
|
||||||
response: Response<SuccessResponse>
|
response: Response<SuccessResponse>
|
||||||
) {
|
) {
|
||||||
if (response.body() != null && response.body()!!.isSuccess) {
|
if (response.body() != null && response.body()!!.isSuccess) {
|
||||||
firebaseAnalytics.logEvent(FirebaseAnalytics.Event.LOGIN, Bundle())
|
goToMain()
|
||||||
goToMain()
|
} else {
|
||||||
} else {
|
preferenceError(Exception("No response body..."))
|
||||||
preferenceError(Exception("No response body..."))
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
|
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
|
||||||
preferenceError(t)
|
preferenceError(t)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
showProgress(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,28 +249,28 @@ class LoginActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
loginForm.visibility = if (show) View.GONE else View.VISIBLE
|
loginForm.visibility = if (show) View.GONE else View.VISIBLE
|
||||||
loginForm
|
loginForm
|
||||||
.animate()
|
.animate()
|
||||||
.setDuration(shortAnimTime.toLong())
|
.setDuration(shortAnimTime.toLong())
|
||||||
.alpha(
|
.alpha(
|
||||||
if (show) 0F else 1F
|
if (show) 0F else 1F
|
||||||
).setListener(object : AnimatorListenerAdapter() {
|
).setListener(object : AnimatorListenerAdapter() {
|
||||||
override fun onAnimationEnd(animation: Animator) {
|
override fun onAnimationEnd(animation: Animator) {
|
||||||
loginForm.visibility = if (show) View.GONE else View.VISIBLE
|
loginForm.visibility = if (show) View.GONE else View.VISIBLE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
loginProgress.visibility = if (show) View.VISIBLE else View.GONE
|
loginProgress.visibility = if (show) View.VISIBLE else View.GONE
|
||||||
loginProgress
|
loginProgress
|
||||||
.animate()
|
.animate()
|
||||||
.setDuration(shortAnimTime.toLong())
|
.setDuration(shortAnimTime.toLong())
|
||||||
.alpha(
|
.alpha(
|
||||||
if (show) 1F else 0F
|
if (show) 1F else 0F
|
||||||
).setListener(object : AnimatorListenerAdapter() {
|
).setListener(object : AnimatorListenerAdapter() {
|
||||||
override fun onAnimationEnd(animation: Animator) {
|
override fun onAnimationEnd(animation: Animator) {
|
||||||
loginProgress.visibility = if (show) View.VISIBLE else View.GONE
|
loginProgress.visibility = if (show) View.VISIBLE else View.GONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -281,10 +284,10 @@ class LoginActivity : AppCompatActivity() {
|
|||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
R.id.about -> {
|
R.id.about -> {
|
||||||
LibsBuilder()
|
LibsBuilder()
|
||||||
.withActivityStyle(Libs.ActivityStyle.LIGHT_DARK_TOOLBAR)
|
.withActivityStyle(Libs.ActivityStyle.LIGHT_DARK_TOOLBAR)
|
||||||
.withAboutIconShown(true)
|
.withAboutIconShown(true)
|
||||||
.withAboutVersionShown(true)
|
.withAboutVersionShown(true)
|
||||||
.start(this)
|
.start(this)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
R.id.login_debug -> {
|
R.id.login_debug -> {
|
||||||
|
@ -3,7 +3,7 @@ package apps.amine.bou.readerforselfoss
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.preference.PreferenceManager
|
import android.preference.PreferenceManager
|
||||||
import android.support.v7.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
@ -11,17 +11,9 @@ class MainActivity : AppCompatActivity() {
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_main)
|
setContentView(R.layout.activity_main)
|
||||||
|
|
||||||
if (PreferenceManager.getDefaultSharedPreferences(baseContext).getBoolean(
|
val intent = Intent(this, LoginActivity::class.java)
|
||||||
"firstStart",
|
|
||||||
true
|
|
||||||
)) {
|
|
||||||
val i = Intent(this@MainActivity, IntroActivity::class.java)
|
|
||||||
startActivity(i)
|
|
||||||
} else {
|
|
||||||
val intent = Intent(this, LoginActivity::class.java)
|
|
||||||
startActivity(intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
startActivity(intent)
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,36 +1,55 @@
|
|||||||
package apps.amine.bou.readerforselfoss
|
package apps.amine.bou.readerforselfoss
|
||||||
|
|
||||||
|
import android.app.NotificationChannel
|
||||||
|
import android.app.NotificationManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
import android.preference.PreferenceManager
|
import android.preference.PreferenceManager
|
||||||
import android.support.multidex.MultiDexApplication
|
import androidx.multidex.MultiDexApplication
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import apps.amine.bou.readerforselfoss.utils.Config
|
import apps.amine.bou.readerforselfoss.utils.Config
|
||||||
import com.anupcowkur.reservoir.Reservoir
|
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.request.RequestOptions
|
import com.bumptech.glide.request.RequestOptions
|
||||||
import com.crashlytics.android.Crashlytics
|
|
||||||
import com.ftinc.scoop.Scoop
|
import com.ftinc.scoop.Scoop
|
||||||
import com.github.stkent.amplify.feedback.DefaultEmailFeedbackCollector
|
import com.github.stkent.amplify.feedback.DefaultEmailFeedbackCollector
|
||||||
import com.github.stkent.amplify.feedback.GooglePlayStoreFeedbackCollector
|
import com.github.stkent.amplify.feedback.GooglePlayStoreFeedbackCollector
|
||||||
import com.github.stkent.amplify.tracking.Amplify
|
import com.github.stkent.amplify.tracking.Amplify
|
||||||
import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader
|
import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader
|
||||||
import com.mikepenz.materialdrawer.util.DrawerImageLoader
|
import com.mikepenz.materialdrawer.util.DrawerImageLoader
|
||||||
import io.fabric.sdk.android.Fabric
|
import 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.io.IOException
|
||||||
import java.util.UUID.randomUUID
|
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() {
|
class MyApp : MultiDexApplication() {
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
Fabric.with(this, Crashlytics())
|
|
||||||
|
|
||||||
initAmplify()
|
initAmplify()
|
||||||
|
|
||||||
initCache()
|
|
||||||
|
|
||||||
val prefs = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
|
val prefs = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
|
||||||
if (prefs.getString("unique_id", "").isEmpty()) {
|
if (prefs.getString("unique_id", "").isEmpty()) {
|
||||||
val editor = prefs.edit()
|
val editor = prefs.edit()
|
||||||
@ -43,35 +62,54 @@ class MyApp : MultiDexApplication() {
|
|||||||
initTheme()
|
initTheme()
|
||||||
|
|
||||||
tryToHandleBug()
|
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() {
|
private fun initAmplify() {
|
||||||
Amplify.initSharedInstance(this)
|
Amplify.initSharedInstance(this)
|
||||||
.setPositiveFeedbackCollectors(GooglePlayStoreFeedbackCollector())
|
.setPositiveFeedbackCollectors(GooglePlayStoreFeedbackCollector())
|
||||||
.setCriticalFeedbackCollectors(DefaultEmailFeedbackCollector(BuildConfig.FEEDBACK_EMAIL))
|
.setCriticalFeedbackCollectors(DefaultEmailFeedbackCollector(Config.feedbackEmail))
|
||||||
.applyAllDefaultRules()
|
.applyAllDefaultRules()
|
||||||
}
|
|
||||||
|
|
||||||
private fun initCache() {
|
|
||||||
try {
|
|
||||||
Reservoir.init(this, 8192) //in bytes
|
|
||||||
} catch (e: IOException) {
|
|
||||||
//failure
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initDrawerImageLoader() {
|
private fun initDrawerImageLoader() {
|
||||||
DrawerImageLoader.init(object : AbstractDrawerImageLoader() {
|
DrawerImageLoader.init(object : AbstractDrawerImageLoader() {
|
||||||
override fun set(
|
override fun set(
|
||||||
imageView: ImageView?,
|
imageView: ImageView?,
|
||||||
uri: Uri?,
|
uri: Uri?,
|
||||||
placeholder: Drawable?,
|
placeholder: Drawable?,
|
||||||
tag: String?
|
tag: String?
|
||||||
) {
|
) {
|
||||||
Glide.with(imageView?.context)
|
Glide.with(imageView?.context)
|
||||||
.load(uri)
|
.load(uri)
|
||||||
.apply(RequestOptions.fitCenterTransform().placeholder(placeholder))
|
.apply(RequestOptions.fitCenterTransform().placeholder(placeholder))
|
||||||
.into(imageView)
|
.into(imageView)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun cancel(imageView: ImageView?) {
|
override fun cancel(imageView: ImageView?) {
|
||||||
@ -86,22 +124,10 @@ class MyApp : MultiDexApplication() {
|
|||||||
|
|
||||||
private fun initTheme() {
|
private fun initTheme() {
|
||||||
Scoop.waffleCone()
|
Scoop.waffleCone()
|
||||||
.addFlavor(getString(R.string.default_theme), R.style.NoBar, true)
|
.addFlavor(getString(R.string.default_theme), R.style.NoBar, true)
|
||||||
.addFlavor(getString(R.string.default_dark_theme), R.style.NoBarDark)
|
.addFlavor(getString(R.string.default_dark_theme), R.style.NoBarDark, false)
|
||||||
.addFlavor(getString(R.string.teal_orange_theme), R.style.NoBarTealOrange)
|
.setSharedPreferences(PreferenceManager.getDefaultSharedPreferences(this))
|
||||||
.addFlavor(getString(R.string.teal_orange_dark_theme), R.style.NoBarTealOrangeDark)
|
.initialize()
|
||||||
.addFlavor(getString(R.string.cyan_pink_theme), R.style.NoBarCyanPink)
|
|
||||||
.addFlavor(getString(R.string.cyan_pink_dark_theme), R.style.NoBarCyanPinkDark)
|
|
||||||
.addFlavor(getString(R.string.grey_orange_theme), R.style.NoBarGreyOrange)
|
|
||||||
.addFlavor(getString(R.string.grey_orange_dark_theme), R.style.NoBarGreyOrangeDark)
|
|
||||||
.addFlavor(getString(R.string.blue_amber_theme), R.style.NoBarBlueAmber)
|
|
||||||
.addFlavor(getString(R.string.blue_amber_dark_theme), R.style.NoBarBlueAmberDark)
|
|
||||||
.addFlavor(getString(R.string.indigo_pink_theme), R.style.NoBarIndigoPink)
|
|
||||||
.addFlavor(getString(R.string.indigo_pink_dark_theme), R.style.NoBarIndigoPinkDark)
|
|
||||||
.addFlavor(getString(R.string.red_teal_theme), R.style.NoBarRedTeal)
|
|
||||||
.addFlavor(getString(R.string.red_teal_dark_theme), R.style.NoBarRedTealDark)
|
|
||||||
.setSharedPreferences(PreferenceManager.getDefaultSharedPreferences(this))
|
|
||||||
.initialize()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun tryToHandleBug() {
|
private fun tryToHandleBug() {
|
||||||
@ -109,8 +135,8 @@ class MyApp : MultiDexApplication() {
|
|||||||
|
|
||||||
Thread.setDefaultUncaughtExceptionHandler { thread, e ->
|
Thread.setDefaultUncaughtExceptionHandler { thread, e ->
|
||||||
if (e is java.lang.NoClassDefFoundError && e.stackTrace.asList().any {
|
if (e is java.lang.NoClassDefFoundError && e.stackTrace.asList().any {
|
||||||
it.toString().contains("android.view.ViewDebug")
|
it.toString().contains("android.view.ViewDebug")
|
||||||
}) {
|
}) {
|
||||||
Unit
|
Unit
|
||||||
} else {
|
} else {
|
||||||
oldHandler.uncaughtException(thread, e)
|
oldHandler.uncaughtException(thread, e)
|
||||||
|
@ -1,139 +1,192 @@
|
|||||||
package apps.amine.bou.readerforselfoss
|
package apps.amine.bou.readerforselfoss
|
||||||
|
|
||||||
import android.content.Context
|
import android.graphics.drawable.ColorDrawable
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.preference.PreferenceManager
|
import android.preference.PreferenceManager
|
||||||
import android.support.v4.app.FragmentManager
|
import androidx.fragment.app.FragmentManager
|
||||||
import android.support.v4.app.FragmentStatePagerAdapter
|
import androidx.fragment.app.FragmentStatePagerAdapter
|
||||||
import android.support.v4.view.ViewPager
|
import androidx.core.content.ContextCompat
|
||||||
import android.support.v7.app.AppCompatActivity
|
import androidx.viewpager.widget.ViewPager
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
|
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.Item
|
||||||
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
|
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
|
||||||
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
|
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
|
||||||
import apps.amine.bou.readerforselfoss.fragments.ArticleFragment
|
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.transformers.DepthPageTransformer
|
||||||
import apps.amine.bou.readerforselfoss.utils.Config
|
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.succeeded
|
||||||
import com.crashlytics.android.Crashlytics
|
import apps.amine.bou.readerforselfoss.utils.toggleStar
|
||||||
import com.ftinc.scoop.Scoop
|
import com.ftinc.scoop.Scoop
|
||||||
import kotlinx.android.synthetic.main.activity_reader.*
|
import kotlinx.android.synthetic.main.activity_reader.*
|
||||||
import me.relex.circleindicator.CircleIndicator
|
import me.relex.circleindicator.CircleIndicator
|
||||||
|
import org.acra.ACRA
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
import retrofit2.Callback
|
import retrofit2.Callback
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
|
import kotlin.concurrent.thread
|
||||||
|
|
||||||
class ReaderActivity : AppCompatActivity() {
|
class ReaderActivity : AppCompatActivity() {
|
||||||
|
|
||||||
private var markOnScroll: Boolean = false
|
private var markOnScroll: Boolean = false
|
||||||
|
private var debugReadingItems: Boolean = false
|
||||||
|
private var currentItem: Int = 0
|
||||||
|
private lateinit var userIdentifier: String
|
||||||
|
|
||||||
private var useWebview: Boolean = false
|
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?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
Scoop.getInstance().apply(this)
|
|
||||||
setContentView(R.layout.activity_reader)
|
setContentView(R.layout.activity_reader)
|
||||||
|
|
||||||
|
db = Room.databaseBuilder(
|
||||||
|
applicationContext,
|
||||||
|
AppDatabase::class.java, "selfoss-database"
|
||||||
|
).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).build()
|
||||||
|
|
||||||
|
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)
|
setSupportActionBar(toolBar)
|
||||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
supportActionBar?.setDisplayShowHomeEnabled(true)
|
supportActionBar?.setDisplayShowHomeEnabled(true)
|
||||||
|
|
||||||
val settings = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
|
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
val sharedPref = PreferenceManager.getDefaultSharedPreferences(this)
|
|
||||||
|
|
||||||
val debugReadingItems = sharedPref.getBoolean("read_debug", false)
|
debugReadingItems = prefs.getBoolean("read_debug", false)
|
||||||
val userIdentifier = sharedPref.getString("unique_id", "")
|
userIdentifier = prefs.getString("unique_id", "")
|
||||||
markOnScroll = sharedPref.getBoolean("mark_on_scroll", false)
|
markOnScroll = prefs.getBoolean("mark_on_scroll", false)
|
||||||
useWebview = sharedPref.getBoolean("prefer_webview_in_article_viewer", false)
|
|
||||||
|
|
||||||
|
api = SelfossApi(
|
||||||
|
this,
|
||||||
|
this@ReaderActivity,
|
||||||
|
prefs.getBoolean("isSelfSignedCert", false),
|
||||||
|
prefs.getBoolean("should_log_everything", false)
|
||||||
|
)
|
||||||
|
|
||||||
if (allItems.isEmpty()) {
|
if (allItems.isEmpty()) {
|
||||||
Crashlytics.setUserIdentifier(userIdentifier)
|
|
||||||
Crashlytics.log(
|
|
||||||
100,
|
|
||||||
"READER_ITEMS_EMPTY",
|
|
||||||
"Items empty when trying to open the Article Reader. Was (static) companion object field set ?"
|
|
||||||
)
|
|
||||||
Crashlytics.logException(Exception("Empty items on Reader Activity."))
|
|
||||||
|
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
val api = SelfossApi(
|
currentItem = intent.getIntExtra("currentItem", 0)
|
||||||
this,
|
|
||||||
this@ReaderActivity,
|
|
||||||
settings.getBoolean("isSelfSignedCert", false),
|
|
||||||
sharedPref.getBoolean("should_log_everything", false)
|
|
||||||
)
|
|
||||||
|
|
||||||
val currentItem = intent.getIntExtra("currentItem", 0)
|
readItem(allItems[currentItem])
|
||||||
|
|
||||||
var adapter = ScreenSlidePagerAdapter(supportFragmentManager)
|
pager.adapter =
|
||||||
pager.adapter = adapter
|
ScreenSlidePagerAdapter(supportFragmentManager, AppColors(this@ReaderActivity))
|
||||||
pager.currentItem = currentItem
|
pager.currentItem = currentItem
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
|
||||||
|
notifyAdapter()
|
||||||
|
|
||||||
pager.setPageTransformer(true, DepthPageTransformer())
|
pager.setPageTransformer(true, DepthPageTransformer())
|
||||||
(indicator as CircleIndicator).setViewPager(pager)
|
(indicator as CircleIndicator).setViewPager(pager)
|
||||||
|
|
||||||
if (markOnScroll) {
|
pager.addOnPageChangeListener(
|
||||||
pager.addOnPageChangeListener(
|
object : ViewPager.SimpleOnPageChangeListener() {
|
||||||
object : ViewPager.SimpleOnPageChangeListener() {
|
|
||||||
var isLastItem = false
|
|
||||||
|
|
||||||
override fun onPageSelected(position: Int) {
|
override fun onPageSelected(position: Int) {
|
||||||
isLastItem = (position === (allItems.size - 1))
|
|
||||||
|
if (allItems[position].starred) {
|
||||||
|
canRemoveFromFavorite()
|
||||||
|
} else {
|
||||||
|
canFavorite()
|
||||||
|
}
|
||||||
|
readItem(allItems[pager.currentItem])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 onPageScrollStateChanged(state: Int) {
|
override fun onFailure(
|
||||||
if (state === ViewPager.SCROLL_STATE_DRAGGING || (state === ViewPager.SCROLL_STATE_IDLE && isLastItem)) {
|
call: Call<SuccessResponse>,
|
||||||
api.markItem(allItems[pager.currentItem].id).enqueue(
|
t: Throwable
|
||||||
object : Callback<SuccessResponse> {
|
) {
|
||||||
override fun onResponse(
|
thread {
|
||||||
call: Call<SuccessResponse>,
|
db.itemsDao().insertAllItems(item.toEntity())
|
||||||
response: Response<SuccessResponse>
|
}
|
||||||
) {
|
if (debugReadingItems) {
|
||||||
if (!response.succeeded() && debugReadingItems) {
|
ACRA.getErrorReporter()
|
||||||
val message =
|
.maybeHandleSilentException(t, this@ReaderActivity)
|
||||||
"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 ?"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(
|
|
||||||
call: Call<SuccessResponse>,
|
|
||||||
t: Throwable
|
|
||||||
) {
|
|
||||||
if (debugReadingItems) {
|
|
||||||
Crashlytics.setUserIdentifier(userIdentifier)
|
|
||||||
Crashlytics.log(
|
|
||||||
100,
|
|
||||||
"READ_DEBUG_ERROR",
|
|
||||||
t.message
|
|
||||||
)
|
|
||||||
Crashlytics.logException(t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
thread {
|
||||||
|
db.actionsDao().insertAllActions(ActionEntity(item.id, true, false, false, false))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun notifyAdapter() {
|
||||||
|
(pager.adapter as ScreenSlidePagerAdapter).notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause()
|
super.onPause()
|
||||||
if (markOnScroll) {
|
if (markOnScroll) {
|
||||||
@ -146,22 +199,120 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
oldInstanceState!!.clear()
|
oldInstanceState!!.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class ScreenSlidePagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm) {
|
private inner class ScreenSlidePagerAdapter(fm: FragmentManager, val appColors: AppColors) :
|
||||||
|
FragmentStatePagerAdapter(fm) {
|
||||||
|
|
||||||
override fun getCount(): Int {
|
override fun getCount(): Int {
|
||||||
return allItems.size
|
return allItems.size
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItem(position: Int): ArticleFragment {
|
override fun getItem(position: Int): ArticleFragment {
|
||||||
return ArticleFragment.newInstance(position, allItems, useWebview)
|
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 {
|
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) {
|
when (item.itemId) {
|
||||||
android.R.id.home -> {
|
android.R.id.home -> {
|
||||||
onBackPressed()
|
onBackPressed()
|
||||||
return true
|
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)
|
return super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,19 @@
|
|||||||
package apps.amine.bou.readerforselfoss
|
package apps.amine.bou.readerforselfoss
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.res.ColorStateList
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.preference.PreferenceManager
|
import android.preference.PreferenceManager
|
||||||
import android.support.v7.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import android.support.v7.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import apps.amine.bou.readerforselfoss.adapters.SourcesListAdapter
|
import apps.amine.bou.readerforselfoss.adapters.SourcesListAdapter
|
||||||
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
|
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
|
||||||
import apps.amine.bou.readerforselfoss.api.selfoss.Sources
|
import apps.amine.bou.readerforselfoss.api.selfoss.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 com.ftinc.scoop.Scoop
|
||||||
import kotlinx.android.synthetic.main.activity_sources.*
|
import kotlinx.android.synthetic.main.activity_sources.*
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
@ -17,14 +22,27 @@ import retrofit2.Response
|
|||||||
|
|
||||||
class SourcesActivity : AppCompatActivity() {
|
class SourcesActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
private lateinit var appColors: AppColors
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
appColors = AppColors(this@SourcesActivity)
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
Scoop.getInstance().apply(this)
|
|
||||||
setContentView(R.layout.activity_sources)
|
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)
|
setSupportActionBar(toolbar)
|
||||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
supportActionBar?.setDisplayShowHomeEnabled(true)
|
supportActionBar?.setDisplayShowHomeEnabled(true)
|
||||||
|
|
||||||
|
fab.rippleColor = appColors.colorAccentDark
|
||||||
|
fab.backgroundTintList = ColorStateList.valueOf(appColors.colorAccent)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStop() {
|
override fun onStop() {
|
||||||
@ -39,44 +57,46 @@ class SourcesActivity : AppCompatActivity() {
|
|||||||
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
|
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
|
|
||||||
val api = SelfossApi(
|
val api = SelfossApi(
|
||||||
this,
|
this,
|
||||||
this@SourcesActivity,
|
this@SourcesActivity,
|
||||||
prefs.getBoolean("isSelfSignedCert", false),
|
prefs.getBoolean("isSelfSignedCert", false),
|
||||||
prefs.getBoolean("should_log_everything", false)
|
prefs.getBoolean("should_log_everything", false)
|
||||||
)
|
)
|
||||||
var items: ArrayList<Sources> = ArrayList()
|
var items: ArrayList<Source> = ArrayList()
|
||||||
|
|
||||||
recyclerView.setHasFixedSize(true)
|
recyclerView.setHasFixedSize(true)
|
||||||
recyclerView.layoutManager = mLayoutManager
|
recyclerView.layoutManager = mLayoutManager
|
||||||
|
|
||||||
api.sources.enqueue(object : Callback<List<Sources>> {
|
if (this@SourcesActivity.isNetworkAccessible(this@SourcesActivity.findViewById(R.id.recyclerView))) {
|
||||||
override fun onResponse(
|
api.sources.enqueue(object : Callback<List<Source>> {
|
||||||
call: Call<List<Sources>>,
|
override fun onResponse(
|
||||||
response: Response<List<Sources>>
|
call: Call<List<Source>>,
|
||||||
) {
|
response: Response<List<Source>>
|
||||||
if (response.body() != null && response.body()!!.isNotEmpty()) {
|
) {
|
||||||
items = response.body() as ArrayList<Sources>
|
if (response.body() != null && response.body()!!.isNotEmpty()) {
|
||||||
}
|
items = response.body() as ArrayList<Source>
|
||||||
val mAdapter = SourcesListAdapter(this@SourcesActivity, items, api)
|
}
|
||||||
recyclerView.adapter = mAdapter
|
val mAdapter = SourcesListAdapter(this@SourcesActivity, items, api)
|
||||||
mAdapter.notifyDataSetChanged()
|
recyclerView.adapter = mAdapter
|
||||||
if (items.isEmpty()) {
|
mAdapter.notifyDataSetChanged()
|
||||||
Toast.makeText(
|
if (items.isEmpty()) {
|
||||||
|
Toast.makeText(
|
||||||
this@SourcesActivity,
|
this@SourcesActivity,
|
||||||
R.string.nothing_here,
|
R.string.nothing_here,
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(call: Call<List<Sources>>, t: Throwable) {
|
override fun onFailure(call: Call<List<Source>>, t: Throwable) {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
this@SourcesActivity,
|
this@SourcesActivity,
|
||||||
R.string.cant_get_sources,
|
R.string.cant_get_sources,
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fab.setOnClickListener {
|
fab.setOnClickListener {
|
||||||
startActivity(Intent(this@SourcesActivity, AddSourceActivity::class.java))
|
startActivity(Intent(this@SourcesActivity, AddSourceActivity::class.java))
|
||||||
|
@ -2,58 +2,61 @@ package apps.amine.bou.readerforselfoss.adapters
|
|||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Color
|
import androidx.cardview.widget.CardView
|
||||||
import android.support.design.widget.Snackbar
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import android.support.v7.widget.CardView
|
|
||||||
import android.support.v7.widget.RecyclerView
|
|
||||||
import android.text.Html
|
import android.text.Html
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView.ScaleType
|
import android.widget.ImageView.ScaleType
|
||||||
import android.widget.TextView
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import apps.amine.bou.readerforselfoss.R
|
import apps.amine.bou.readerforselfoss.R
|
||||||
import apps.amine.bou.readerforselfoss.api.selfoss.Item
|
import apps.amine.bou.readerforselfoss.api.selfoss.Item
|
||||||
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
|
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
|
||||||
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
|
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
|
||||||
|
import apps.amine.bou.readerforselfoss.persistence.database.AppDatabase
|
||||||
|
import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity
|
||||||
import apps.amine.bou.readerforselfoss.themes.AppColors
|
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.buildCustomTabsIntent
|
||||||
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
|
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
|
||||||
import apps.amine.bou.readerforselfoss.utils.glide.bitmapCenterCrop
|
import apps.amine.bou.readerforselfoss.utils.glide.bitmapCenterCrop
|
||||||
import apps.amine.bou.readerforselfoss.utils.glide.circularBitmapDrawable
|
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.openInBrowserAsNewTask
|
||||||
import apps.amine.bou.readerforselfoss.utils.openItemUrl
|
import apps.amine.bou.readerforselfoss.utils.openItemUrl
|
||||||
import apps.amine.bou.readerforselfoss.utils.shareLink
|
import apps.amine.bou.readerforselfoss.utils.shareLink
|
||||||
import apps.amine.bou.readerforselfoss.utils.sourceAndDateText
|
import apps.amine.bou.readerforselfoss.utils.sourceAndDateText
|
||||||
import apps.amine.bou.readerforselfoss.utils.succeeded
|
|
||||||
import apps.amine.bou.readerforselfoss.utils.toTextDrawableString
|
import apps.amine.bou.readerforselfoss.utils.toTextDrawableString
|
||||||
import com.amulyakhare.textdrawable.TextDrawable
|
import com.amulyakhare.textdrawable.TextDrawable
|
||||||
import com.amulyakhare.textdrawable.util.ColorGenerator
|
import com.amulyakhare.textdrawable.util.ColorGenerator
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.crashlytics.android.Crashlytics
|
|
||||||
import com.like.LikeButton
|
import com.like.LikeButton
|
||||||
import com.like.OnLikeListener
|
import com.like.OnLikeListener
|
||||||
import kotlinx.android.synthetic.main.card_item.view.*
|
import kotlinx.android.synthetic.main.card_item.view.*
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
import retrofit2.Callback
|
import retrofit2.Callback
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
|
import kotlin.concurrent.thread
|
||||||
|
|
||||||
class ItemCardAdapter(
|
class ItemCardAdapter(
|
||||||
private val app: Activity,
|
override val app: Activity,
|
||||||
private val items: ArrayList<Item>,
|
override var items: ArrayList<Item>,
|
||||||
private val api: SelfossApi,
|
override val api: SelfossApi,
|
||||||
private val helper: CustomTabActivityHelper,
|
override val db: AppDatabase,
|
||||||
private val internalBrowser: Boolean,
|
private val helper: CustomTabActivityHelper,
|
||||||
private val articleViewer: Boolean,
|
private val internalBrowser: Boolean,
|
||||||
private val fullHeightCards: Boolean,
|
private val articleViewer: Boolean,
|
||||||
private val appColors: AppColors,
|
private val fullHeightCards: Boolean,
|
||||||
val debugReadingItems: Boolean,
|
override val appColors: AppColors,
|
||||||
val userIdentifier: String
|
override val debugReadingItems: Boolean,
|
||||||
) : RecyclerView.Adapter<ItemCardAdapter.ViewHolder>() {
|
override val userIdentifier: String,
|
||||||
|
override val updateItems: (ArrayList<Item>) -> Unit
|
||||||
|
) : ItemsAdapter<ItemCardAdapter.ViewHolder>() {
|
||||||
private val c: Context = app.baseContext
|
private val c: Context = app.baseContext
|
||||||
private val generator: ColorGenerator = ColorGenerator.MATERIAL
|
private val generator: ColorGenerator = ColorGenerator.MATERIAL
|
||||||
private val imageMaxHeight: Int = c.resources.getDimension(R.dimen.card_image_max_height).toInt()
|
private val imageMaxHeight: Int =
|
||||||
|
c.resources.getDimension(R.dimen.card_image_max_height).toInt()
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
val v = LayoutInflater.from(c).inflate(R.layout.card_item, parent, false) as CardView
|
val v = LayoutInflater.from(c).inflate(R.layout.card_item, parent, false) as CardView
|
||||||
@ -66,6 +69,9 @@ class ItemCardAdapter(
|
|||||||
|
|
||||||
holder.mView.favButton.isLiked = itm.starred
|
holder.mView.favButton.isLiked = itm.starred
|
||||||
holder.mView.title.text = Html.fromHtml(itm.title)
|
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()
|
holder.mView.sourceTitleAndDate.text = itm.sourceAndDateText()
|
||||||
|
|
||||||
@ -87,10 +93,10 @@ class ItemCardAdapter(
|
|||||||
val color = generator.getColor(itm.sourcetitle)
|
val color = generator.getColor(itm.sourcetitle)
|
||||||
|
|
||||||
val drawable =
|
val drawable =
|
||||||
TextDrawable
|
TextDrawable
|
||||||
.builder()
|
.builder()
|
||||||
.round()
|
.round()
|
||||||
.build(itm.sourcetitle.toTextDrawableString(), color)
|
.build(itm.sourcetitle.toTextDrawableString(c), color)
|
||||||
holder.mView.sourceImage.setImageDrawable(drawable)
|
holder.mView.sourceImage.setImageDrawable(drawable)
|
||||||
} else {
|
} else {
|
||||||
c.circularBitmapDrawable(itm.getIcon(c), holder.mView.sourceImage)
|
c.circularBitmapDrawable(itm.getIcon(c), holder.mView.sourceImage)
|
||||||
@ -103,89 +109,9 @@ class ItemCardAdapter(
|
|||||||
return items.size
|
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) {
|
inner class ViewHolder(val mView: CardView) : RecyclerView.ViewHolder(mView) {
|
||||||
init {
|
init {
|
||||||
mView.setCardBackgroundColor(appColors.cardBackground)
|
mView.setCardBackgroundColor(appColors.cardBackgroundColor)
|
||||||
handleClickListeners()
|
handleClickListeners()
|
||||||
handleCustomTabActions()
|
handleCustomTabActions()
|
||||||
}
|
}
|
||||||
@ -195,48 +121,60 @@ class ItemCardAdapter(
|
|||||||
mView.favButton.setOnLikeListener(object : OnLikeListener {
|
mView.favButton.setOnLikeListener(object : OnLikeListener {
|
||||||
override fun liked(likeButton: LikeButton) {
|
override fun liked(likeButton: LikeButton) {
|
||||||
val (id) = items[adapterPosition]
|
val (id) = items[adapterPosition]
|
||||||
api.starrItem(id).enqueue(object : Callback<SuccessResponse> {
|
if (c.isNetworkAccessible(null)) {
|
||||||
override fun onResponse(
|
api.starrItem(id).enqueue(object : Callback<SuccessResponse> {
|
||||||
|
override fun onResponse(
|
||||||
call: Call<SuccessResponse>,
|
call: Call<SuccessResponse>,
|
||||||
response: Response<SuccessResponse>
|
response: Response<SuccessResponse>
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailure(
|
override fun onFailure(
|
||||||
call: Call<SuccessResponse>,
|
call: Call<SuccessResponse>,
|
||||||
t: Throwable
|
t: Throwable
|
||||||
) {
|
) {
|
||||||
mView.favButton.isLiked = false
|
mView.favButton.isLiked = false
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
c,
|
c,
|
||||||
R.string.cant_mark_favortie,
|
R.string.cant_mark_favortie,
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
thread {
|
||||||
|
db.actionsDao().insertAllActions(ActionEntity(id, false, false, true, false))
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun unLiked(likeButton: LikeButton) {
|
override fun unLiked(likeButton: LikeButton) {
|
||||||
val (id) = items[adapterPosition]
|
val (id) = items[adapterPosition]
|
||||||
api.unstarrItem(id).enqueue(object : Callback<SuccessResponse> {
|
if (c.isNetworkAccessible(null)) {
|
||||||
override fun onResponse(
|
api.unstarrItem(id).enqueue(object : Callback<SuccessResponse> {
|
||||||
|
override fun onResponse(
|
||||||
call: Call<SuccessResponse>,
|
call: Call<SuccessResponse>,
|
||||||
response: Response<SuccessResponse>
|
response: Response<SuccessResponse>
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailure(
|
override fun onFailure(
|
||||||
call: Call<SuccessResponse>,
|
call: Call<SuccessResponse>,
|
||||||
t: Throwable
|
t: Throwable
|
||||||
) {
|
) {
|
||||||
mView.favButton.isLiked = true
|
mView.favButton.isLiked = true
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
c,
|
c,
|
||||||
R.string.cant_unmark_favortie,
|
R.string.cant_unmark_favortie,
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
thread {
|
||||||
|
db.actionsDao().insertAllActions(ActionEntity(id, false, false, false, true))
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -255,13 +193,13 @@ class ItemCardAdapter(
|
|||||||
|
|
||||||
mView.setOnClickListener {
|
mView.setOnClickListener {
|
||||||
c.openItemUrl(
|
c.openItemUrl(
|
||||||
items,
|
items,
|
||||||
adapterPosition,
|
adapterPosition,
|
||||||
items[adapterPosition].getLinkDecoded(),
|
items[adapterPosition].getLinkDecoded(),
|
||||||
customTabsIntent,
|
customTabsIntent,
|
||||||
internalBrowser,
|
internalBrowser,
|
||||||
articleViewer,
|
articleViewer,
|
||||||
app
|
app
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,13 +2,14 @@ package apps.amine.bou.readerforselfoss.adapters
|
|||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Color
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import android.support.constraint.ConstraintLayout
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import android.support.design.widget.Snackbar
|
|
||||||
import android.support.v7.widget.RecyclerView
|
|
||||||
import android.text.Html
|
import android.text.Html
|
||||||
|
import android.text.Spannable
|
||||||
|
import android.text.style.ClickableSpan
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
@ -17,6 +18,9 @@ import apps.amine.bou.readerforselfoss.R
|
|||||||
import apps.amine.bou.readerforselfoss.api.selfoss.Item
|
import apps.amine.bou.readerforselfoss.api.selfoss.Item
|
||||||
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
|
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
|
||||||
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
|
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
|
||||||
|
import apps.amine.bou.readerforselfoss.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.buildCustomTabsIntent
|
||||||
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
|
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
|
||||||
import apps.amine.bou.readerforselfoss.utils.glide.bitmapCenterCrop
|
import apps.amine.bou.readerforselfoss.utils.glide.bitmapCenterCrop
|
||||||
@ -25,10 +29,9 @@ import apps.amine.bou.readerforselfoss.utils.openInBrowserAsNewTask
|
|||||||
import apps.amine.bou.readerforselfoss.utils.openItemUrl
|
import apps.amine.bou.readerforselfoss.utils.openItemUrl
|
||||||
import apps.amine.bou.readerforselfoss.utils.shareLink
|
import apps.amine.bou.readerforselfoss.utils.shareLink
|
||||||
import apps.amine.bou.readerforselfoss.utils.sourceAndDateText
|
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.TextDrawable
|
||||||
import com.amulyakhare.textdrawable.util.ColorGenerator
|
import com.amulyakhare.textdrawable.util.ColorGenerator
|
||||||
import com.crashlytics.android.Crashlytics
|
|
||||||
import com.like.LikeButton
|
import com.like.LikeButton
|
||||||
import com.like.OnLikeListener
|
import com.like.OnLikeListener
|
||||||
import kotlinx.android.synthetic.main.list_item.view.*
|
import kotlinx.android.synthetic.main.list_item.view.*
|
||||||
@ -39,25 +42,26 @@ import java.util.*
|
|||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
class ItemListAdapter(
|
class ItemListAdapter(
|
||||||
private val app: Activity,
|
override val app: Activity,
|
||||||
private val items: ArrayList<Item>,
|
override var items: ArrayList<Item>,
|
||||||
private val api: SelfossApi,
|
override val api: SelfossApi,
|
||||||
private val helper: CustomTabActivityHelper,
|
override val db: AppDatabase,
|
||||||
private val clickBehavior: Boolean,
|
private val helper: CustomTabActivityHelper,
|
||||||
private val internalBrowser: Boolean,
|
private val internalBrowser: Boolean,
|
||||||
private val articleViewer: Boolean,
|
private val articleViewer: Boolean,
|
||||||
val debugReadingItems: Boolean,
|
override val debugReadingItems: Boolean,
|
||||||
val userIdentifier: String
|
override val userIdentifier: String,
|
||||||
) : RecyclerView.Adapter<ItemListAdapter.ViewHolder>() {
|
override val appColors: AppColors,
|
||||||
|
override val updateItems: (ArrayList<Item>) -> Unit
|
||||||
|
) : ItemsAdapter<ItemListAdapter.ViewHolder>() {
|
||||||
private val generator: ColorGenerator = ColorGenerator.MATERIAL
|
private val generator: ColorGenerator = ColorGenerator.MATERIAL
|
||||||
private val c: Context = app.baseContext
|
private val c: Context = app.baseContext
|
||||||
private val bars: ArrayList<Boolean> = ArrayList(Collections.nCopies(items.size + 1, false))
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
val v = LayoutInflater.from(c).inflate(
|
val v = LayoutInflater.from(c).inflate(
|
||||||
R.layout.list_item,
|
R.layout.list_item,
|
||||||
parent,
|
parent,
|
||||||
false
|
false
|
||||||
) as ConstraintLayout
|
) as ConstraintLayout
|
||||||
return ViewHolder(v)
|
return ViewHolder(v)
|
||||||
}
|
}
|
||||||
@ -66,21 +70,24 @@ class ItemListAdapter(
|
|||||||
val itm = items[position]
|
val itm = items[position]
|
||||||
|
|
||||||
|
|
||||||
holder.mView.favButton.isLiked = itm.starred
|
|
||||||
holder.mView.title.text = Html.fromHtml(itm.title)
|
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()
|
holder.mView.sourceTitleAndDate.text = itm.sourceAndDateText()
|
||||||
|
|
||||||
if (itm.getThumbnail(c).isEmpty()) {
|
if (itm.getThumbnail(c).isEmpty()) {
|
||||||
val sizeInInt = 46
|
val sizeInInt = 46
|
||||||
val sizeInDp = TypedValue.applyDimension(
|
val sizeInDp = TypedValue.applyDimension(
|
||||||
TypedValue.COMPLEX_UNIT_DIP, sizeInInt.toFloat(), c.resources
|
TypedValue.COMPLEX_UNIT_DIP, sizeInInt.toFloat(), c.resources
|
||||||
.displayMetrics
|
.displayMetrics
|
||||||
).toInt()
|
).toInt()
|
||||||
|
|
||||||
val marginInInt = 16
|
val marginInInt = 16
|
||||||
val marginInDp = TypedValue.applyDimension(
|
val marginInDp = TypedValue.applyDimension(
|
||||||
TypedValue.COMPLEX_UNIT_DIP, marginInInt.toFloat(), c.resources
|
TypedValue.COMPLEX_UNIT_DIP, marginInInt.toFloat(), c.resources
|
||||||
.displayMetrics
|
.displayMetrics
|
||||||
).toInt()
|
).toInt()
|
||||||
|
|
||||||
@ -92,14 +99,13 @@ class ItemListAdapter(
|
|||||||
|
|
||||||
if (itm.getIcon(c).isEmpty()) {
|
if (itm.getIcon(c).isEmpty()) {
|
||||||
val color = generator.getColor(itm.sourcetitle)
|
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)
|
holder.mView.itemImage.setImageDrawable(drawable)
|
||||||
} else {
|
} else {
|
||||||
c.circularBitmapDrawable(itm.getIcon(c), holder.mView.itemImage)
|
c.circularBitmapDrawable(itm.getIcon(c), holder.mView.itemImage)
|
||||||
@ -107,208 +113,30 @@ class ItemListAdapter(
|
|||||||
} else {
|
} else {
|
||||||
c.bitmapCenterCrop(itm.getThumbnail(c), holder.mView.itemImage)
|
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
|
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) {
|
inner class ViewHolder(val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
handleClickListeners()
|
|
||||||
handleCustomTabActions()
|
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() {
|
private fun handleCustomTabActions() {
|
||||||
val customTabsIntent = c.buildCustomTabsIntent()
|
val customTabsIntent = c.buildCustomTabsIntent()
|
||||||
helper.bindCustomTabsService(app)
|
helper.bindCustomTabsService(app)
|
||||||
|
|
||||||
|
mView.setOnClickListener {
|
||||||
if (!clickBehavior) {
|
c.openItemUrl(
|
||||||
mView.setOnClickListener {
|
items,
|
||||||
c.openItemUrl(
|
adapterPosition,
|
||||||
items,
|
items[adapterPosition].getLinkDecoded(),
|
||||||
adapterPosition,
|
customTabsIntent,
|
||||||
items[adapterPosition].getLinkDecoded(),
|
internalBrowser,
|
||||||
customTabsIntent,
|
articleViewer,
|
||||||
internalBrowser,
|
app
|
||||||
articleViewer,
|
)
|
||||||
app
|
|
||||||
)
|
|
||||||
}
|
|
||||||
mView.setOnLongClickListener {
|
|
||||||
actionBarShowHide()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mView.setOnClickListener { actionBarShowHide() }
|
|
||||||
mView.setOnLongClickListener {
|
|
||||||
c.openItemUrl(
|
|
||||||
items,
|
|
||||||
adapterPosition,
|
|
||||||
items[adapterPosition].getLinkDecoded(),
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,17 +2,18 @@ package apps.amine.bou.readerforselfoss.adapters
|
|||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.support.constraint.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import android.support.v7.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import apps.amine.bou.readerforselfoss.R
|
import apps.amine.bou.readerforselfoss.R
|
||||||
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
|
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
|
||||||
import apps.amine.bou.readerforselfoss.api.selfoss.Sources
|
import apps.amine.bou.readerforselfoss.api.selfoss.Source
|
||||||
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
|
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
|
||||||
import apps.amine.bou.readerforselfoss.utils.glide.circularBitmapDrawable
|
import apps.amine.bou.readerforselfoss.utils.glide.circularBitmapDrawable
|
||||||
|
import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible
|
||||||
import apps.amine.bou.readerforselfoss.utils.toTextDrawableString
|
import apps.amine.bou.readerforselfoss.utils.toTextDrawableString
|
||||||
import com.amulyakhare.textdrawable.TextDrawable
|
import com.amulyakhare.textdrawable.TextDrawable
|
||||||
import com.amulyakhare.textdrawable.util.ColorGenerator
|
import com.amulyakhare.textdrawable.util.ColorGenerator
|
||||||
@ -22,18 +23,18 @@ import retrofit2.Callback
|
|||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
|
|
||||||
class SourcesListAdapter(
|
class SourcesListAdapter(
|
||||||
private val app: Activity,
|
private val app: Activity,
|
||||||
private val items: ArrayList<Sources>,
|
private val items: ArrayList<Source>,
|
||||||
private val api: SelfossApi
|
private val api: SelfossApi
|
||||||
) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>() {
|
) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>() {
|
||||||
private val c: Context = app.baseContext
|
private val c: Context = app.baseContext
|
||||||
private val generator: ColorGenerator = ColorGenerator.MATERIAL
|
private val generator: ColorGenerator = ColorGenerator.MATERIAL
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
val v = LayoutInflater.from(c).inflate(
|
val v = LayoutInflater.from(c).inflate(
|
||||||
R.layout.source_list_item,
|
R.layout.source_list_item,
|
||||||
parent,
|
parent,
|
||||||
false
|
false
|
||||||
) as ConstraintLayout
|
) as ConstraintLayout
|
||||||
return ViewHolder(v)
|
return ViewHolder(v)
|
||||||
}
|
}
|
||||||
@ -45,10 +46,10 @@ class SourcesListAdapter(
|
|||||||
val color = generator.getColor(itm.title)
|
val color = generator.getColor(itm.title)
|
||||||
|
|
||||||
val drawable =
|
val drawable =
|
||||||
TextDrawable
|
TextDrawable
|
||||||
.builder()
|
.builder()
|
||||||
.round()
|
.round()
|
||||||
.build(itm.title.toTextDrawableString(), color)
|
.build(itm.title.toTextDrawableString(c), color)
|
||||||
holder.mView.itemImage.setImageDrawable(drawable)
|
holder.mView.itemImage.setImageDrawable(drawable)
|
||||||
} else {
|
} else {
|
||||||
c.circularBitmapDrawable(itm.getIcon(c), holder.mView.itemImage)
|
c.circularBitmapDrawable(itm.getIcon(c), holder.mView.itemImage)
|
||||||
@ -70,33 +71,35 @@ class SourcesListAdapter(
|
|||||||
val deleteBtn: Button = mView.findViewById(R.id.deleteBtn)
|
val deleteBtn: Button = mView.findViewById(R.id.deleteBtn)
|
||||||
|
|
||||||
deleteBtn.setOnClickListener {
|
deleteBtn.setOnClickListener {
|
||||||
val (id) = items[adapterPosition]
|
if (c.isNetworkAccessible(null)) {
|
||||||
api.deleteSource(id).enqueue(object : Callback<SuccessResponse> {
|
val (id) = items[adapterPosition]
|
||||||
override fun onResponse(
|
api.deleteSource(id).enqueue(object : Callback<SuccessResponse> {
|
||||||
|
override fun onResponse(
|
||||||
call: Call<SuccessResponse>,
|
call: Call<SuccessResponse>,
|
||||||
response: Response<SuccessResponse>
|
response: Response<SuccessResponse>
|
||||||
) {
|
) {
|
||||||
if (response.body() != null && response.body()!!.isSuccess) {
|
if (response.body() != null && response.body()!!.isSuccess) {
|
||||||
items.removeAt(adapterPosition)
|
items.removeAt(adapterPosition)
|
||||||
notifyItemRemoved(adapterPosition)
|
notifyItemRemoved(adapterPosition)
|
||||||
notifyItemRangeChanged(adapterPosition, itemCount)
|
notifyItemRangeChanged(adapterPosition, itemCount)
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
app,
|
app,
|
||||||
R.string.can_delete_source,
|
R.string.can_delete_source,
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
|
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
app,
|
app,
|
||||||
R.string.can_delete_source,
|
R.string.can_delete_source,
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ import retrofit2.Call
|
|||||||
import retrofit2.Retrofit
|
import retrofit2.Retrofit
|
||||||
import retrofit2.converter.gson.GsonConverterFactory
|
import retrofit2.converter.gson.GsonConverterFactory
|
||||||
|
|
||||||
class MercuryApi(private val key: String, shouldLog: Boolean) {
|
class MercuryApi(shouldLog: Boolean) {
|
||||||
private val service: MercuryService
|
private val service: MercuryService
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@ -21,19 +21,19 @@ class MercuryApi(private val key: String, shouldLog: Boolean) {
|
|||||||
val client = OkHttpClient.Builder().addInterceptor(interceptor).build()
|
val client = OkHttpClient.Builder().addInterceptor(interceptor).build()
|
||||||
|
|
||||||
val gson = GsonBuilder()
|
val gson = GsonBuilder()
|
||||||
.setLenient()
|
.setLenient()
|
||||||
.create()
|
.create()
|
||||||
val retrofit =
|
val retrofit =
|
||||||
Retrofit
|
Retrofit
|
||||||
.Builder()
|
.Builder()
|
||||||
.baseUrl("https://mercury.postlight.com")
|
.baseUrl("https://www.amine-bou.fr")
|
||||||
.client(client)
|
.client(client)
|
||||||
.addConverterFactory(GsonConverterFactory.create(gson))
|
.addConverterFactory(GsonConverterFactory.create(gson))
|
||||||
.build()
|
.build()
|
||||||
service = retrofit.create(MercuryService::class.java)
|
service = retrofit.create(MercuryService::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun parseUrl(url: String): Call<ParsedContent> {
|
fun parseUrl(url: String): Call<ParsedContent> {
|
||||||
return service.parseUrl(url, this.key)
|
return service.parseUrl(url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,39 +5,40 @@ import android.os.Parcelable
|
|||||||
import com.google.gson.annotations.SerializedName
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
class ParsedContent(
|
class ParsedContent(
|
||||||
@SerializedName("title") val title: String,
|
@SerializedName("title") val title: String,
|
||||||
@SerializedName("content") val content: String,
|
@SerializedName("content") val content: String?,
|
||||||
@SerializedName("date_published") val date_published: String,
|
@SerializedName("date_published") val date_published: String,
|
||||||
@SerializedName("lead_image_url") val lead_image_url: String,
|
@SerializedName("lead_image_url") val lead_image_url: String?,
|
||||||
@SerializedName("dek") val dek: String,
|
@SerializedName("dek") val dek: String,
|
||||||
@SerializedName("url") val url: String,
|
@SerializedName("url") val url: String,
|
||||||
@SerializedName("domain") val domain: String,
|
@SerializedName("domain") val domain: String,
|
||||||
@SerializedName("excerpt") val excerpt: String,
|
@SerializedName("excerpt") val excerpt: String,
|
||||||
@SerializedName("total_pages") val total_pages: Int,
|
@SerializedName("total_pages") val total_pages: Int,
|
||||||
@SerializedName("rendered_pages") val rendered_pages: Int,
|
@SerializedName("rendered_pages") val rendered_pages: Int,
|
||||||
@SerializedName("next_page_url") val next_page_url: String
|
@SerializedName("next_page_url") val next_page_url: String
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmField
|
@JvmField
|
||||||
val CREATOR: Parcelable.Creator<ParsedContent> = object : Parcelable.Creator<ParsedContent> {
|
val CREATOR: Parcelable.Creator<ParsedContent> =
|
||||||
override fun createFromParcel(source: Parcel): ParsedContent = ParsedContent(source)
|
object : Parcelable.Creator<ParsedContent> {
|
||||||
override fun newArray(size: Int): Array<ParsedContent?> = arrayOfNulls(size)
|
override fun createFromParcel(source: Parcel): ParsedContent = ParsedContent(source)
|
||||||
}
|
override fun newArray(size: Int): Array<ParsedContent?> = arrayOfNulls(size)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(source: Parcel) : this(
|
constructor(source: Parcel) : this(
|
||||||
title = source.readString(),
|
title = source.readString(),
|
||||||
content = source.readString(),
|
content = source.readString(),
|
||||||
date_published = source.readString(),
|
date_published = source.readString(),
|
||||||
lead_image_url = source.readString(),
|
lead_image_url = source.readString(),
|
||||||
dek = source.readString(),
|
dek = source.readString(),
|
||||||
url = source.readString(),
|
url = source.readString(),
|
||||||
domain = source.readString(),
|
domain = source.readString(),
|
||||||
excerpt = source.readString(),
|
excerpt = source.readString(),
|
||||||
total_pages = source.readInt(),
|
total_pages = source.readInt(),
|
||||||
rendered_pages = source.readInt(),
|
rendered_pages = source.readInt(),
|
||||||
next_page_url = source.readString()
|
next_page_url = source.readString()
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun describeContents() = 0
|
override fun describeContents() = 0
|
||||||
|
@ -6,6 +6,6 @@ import retrofit2.http.Header
|
|||||||
import retrofit2.http.Query
|
import retrofit2.http.Query
|
||||||
|
|
||||||
interface MercuryService {
|
interface MercuryService {
|
||||||
@GET("parser")
|
@GET("parser.php")
|
||||||
fun parseUrl(@Query("url") url: String, @Header("x-api-key") key: String): Call<ParsedContent>
|
fun parseUrl(@Query("link") link: String): Call<ParsedContent>
|
||||||
}
|
}
|
||||||
|
@ -10,13 +10,13 @@ internal class BooleanTypeAdapter : JsonDeserializer<Boolean> {
|
|||||||
|
|
||||||
@Throws(JsonParseException::class)
|
@Throws(JsonParseException::class)
|
||||||
override fun deserialize(
|
override fun deserialize(
|
||||||
json: JsonElement,
|
json: JsonElement,
|
||||||
typeOfT: Type,
|
typeOfT: Type,
|
||||||
context: JsonDeserializationContext
|
context: JsonDeserializationContext
|
||||||
): Boolean? =
|
): Boolean? =
|
||||||
try {
|
try {
|
||||||
json.asInt == 1
|
json.asInt == 1
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
json.asBoolean
|
json.asBoolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,10 +20,10 @@ import retrofit2.converter.gson.GsonConverterFactory
|
|||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
class SelfossApi(
|
class SelfossApi(
|
||||||
c: Context,
|
c: Context,
|
||||||
callingActivity: Activity,
|
callingActivity: Activity?,
|
||||||
isWithSelfSignedCert: Boolean,
|
isWithSelfSignedCert: Boolean,
|
||||||
shouldLog: Boolean
|
shouldLog: Boolean
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private lateinit var service: SelfossService
|
private lateinit var service: SelfossService
|
||||||
@ -32,25 +32,25 @@ class SelfossApi(
|
|||||||
private val password: String
|
private val password: String
|
||||||
|
|
||||||
fun OkHttpClient.Builder.maybeWithSelfSigned(isWithSelfSignedCert: Boolean): OkHttpClient.Builder =
|
fun OkHttpClient.Builder.maybeWithSelfSigned(isWithSelfSignedCert: Boolean): OkHttpClient.Builder =
|
||||||
if (isWithSelfSignedCert) {
|
if (isWithSelfSignedCert) {
|
||||||
getUnsafeHttpClient()
|
getUnsafeHttpClient()
|
||||||
} else {
|
} else {
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Credentials.createAuthenticator(): DispatchingAuthenticator =
|
fun Credentials.createAuthenticator(): DispatchingAuthenticator =
|
||||||
DispatchingAuthenticator.Builder()
|
DispatchingAuthenticator.Builder()
|
||||||
.with("digest", DigestAuthenticator(this))
|
.with("digest", DigestAuthenticator(this))
|
||||||
.with("basic", BasicAuthenticator(this))
|
.with("basic", BasicAuthenticator(this))
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
fun DispatchingAuthenticator.getHttpClien(isWithSelfSignedCert: Boolean): OkHttpClient.Builder {
|
fun DispatchingAuthenticator.getHttpClien(isWithSelfSignedCert: Boolean): OkHttpClient.Builder {
|
||||||
val authCache = ConcurrentHashMap<String, CachingAuthenticator>()
|
val authCache = ConcurrentHashMap<String, CachingAuthenticator>()
|
||||||
return OkHttpClient
|
return OkHttpClient
|
||||||
.Builder()
|
.Builder()
|
||||||
.maybeWithSelfSigned(isWithSelfSignedCert)
|
.maybeWithSelfSigned(isWithSelfSignedCert)
|
||||||
.authenticator(CachingAuthenticatorDecorator(this, authCache))
|
.authenticator(CachingAuthenticatorDecorator(this, authCache))
|
||||||
.addInterceptor(AuthenticationCacheInterceptor(authCache))
|
.addInterceptor(AuthenticationCacheInterceptor(authCache))
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@ -58,16 +58,17 @@ class SelfossApi(
|
|||||||
password = config.userPassword
|
password = config.userPassword
|
||||||
|
|
||||||
val authenticator =
|
val authenticator =
|
||||||
Credentials(
|
Credentials(
|
||||||
config.httpUserLogin,
|
config.httpUserLogin,
|
||||||
config.httpUserPassword
|
config.httpUserPassword
|
||||||
).createAuthenticator()
|
).createAuthenticator()
|
||||||
|
|
||||||
val gson =
|
val gson =
|
||||||
GsonBuilder()
|
GsonBuilder()
|
||||||
.registerTypeAdapter(Boolean::class.javaPrimitiveType, BooleanTypeAdapter())
|
.registerTypeAdapter(Boolean::class.javaPrimitiveType, BooleanTypeAdapter())
|
||||||
.setLenient()
|
.registerTypeAdapter(SelfossTagType::class.java, SelfossTagTypeTypeAdapter())
|
||||||
.create()
|
.setLenient()
|
||||||
|
.create()
|
||||||
|
|
||||||
val logging = HttpLoggingInterceptor()
|
val logging = HttpLoggingInterceptor()
|
||||||
|
|
||||||
@ -83,72 +84,77 @@ class SelfossApi(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
val retrofit =
|
val retrofit =
|
||||||
Retrofit
|
Retrofit
|
||||||
.Builder()
|
.Builder()
|
||||||
.baseUrl(config.baseUrl)
|
.baseUrl(config.baseUrl)
|
||||||
.client(httpClient.build())
|
.client(httpClient.build())
|
||||||
.addConverterFactory(GsonConverterFactory.create(gson))
|
.addConverterFactory(GsonConverterFactory.create(gson))
|
||||||
.build()
|
.build()
|
||||||
service = retrofit.create(SelfossService::class.java)
|
service = retrofit.create(SelfossService::class.java)
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
Config.logoutAndRedirect(c, callingActivity, config.settings.edit(), baseUrlFail = true)
|
if (callingActivity != null) {
|
||||||
|
Config.logoutAndRedirect(c, callingActivity, config.settings.edit(), baseUrlFail = true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun login(): Call<SuccessResponse> =
|
fun login(): Call<SuccessResponse> =
|
||||||
service.loginToSelfoss(config.userLogin, config.userPassword)
|
service.loginToSelfoss(config.userLogin, config.userPassword)
|
||||||
|
|
||||||
fun readItems(
|
fun readItems(
|
||||||
tag: String?,
|
tag: String?,
|
||||||
sourceId: Long?,
|
sourceId: Long?,
|
||||||
search: String?,
|
search: String?,
|
||||||
itemsNumber: Int,
|
itemsNumber: Int,
|
||||||
offset: Int
|
offset: Int
|
||||||
): Call<List<Item>> =
|
): Call<List<Item>> =
|
||||||
getItems("read", tag, sourceId, search, itemsNumber, offset)
|
getItems("read", tag, sourceId, search, itemsNumber, offset)
|
||||||
|
|
||||||
fun newItems(
|
fun newItems(
|
||||||
tag: String?,
|
tag: String?,
|
||||||
sourceId: Long?,
|
sourceId: Long?,
|
||||||
search: String?,
|
search: String?,
|
||||||
itemsNumber: Int,
|
itemsNumber: Int,
|
||||||
offset: Int
|
offset: Int
|
||||||
): Call<List<Item>> =
|
): Call<List<Item>> =
|
||||||
getItems("unread", tag, sourceId, search, itemsNumber, offset)
|
getItems("unread", tag, sourceId, search, itemsNumber, offset)
|
||||||
|
|
||||||
fun starredItems(
|
fun starredItems(
|
||||||
tag: String?,
|
tag: String?,
|
||||||
sourceId: Long?,
|
sourceId: Long?,
|
||||||
search: String?,
|
search: String?,
|
||||||
itemsNumber: Int,
|
itemsNumber: Int,
|
||||||
offset: Int
|
offset: Int
|
||||||
): Call<List<Item>> =
|
): Call<List<Item>> =
|
||||||
getItems("starred", tag, sourceId, search, itemsNumber, offset)
|
getItems("starred", tag, sourceId, search, itemsNumber, offset)
|
||||||
|
|
||||||
|
fun allItems(): Call<List<Item>> =
|
||||||
|
service.allItems(userName, password)
|
||||||
|
|
||||||
private fun getItems(
|
private fun getItems(
|
||||||
type: String,
|
type: String,
|
||||||
tag: String?,
|
tag: String?,
|
||||||
sourceId: Long?,
|
sourceId: Long?,
|
||||||
search: String?,
|
search: String?,
|
||||||
items: Int,
|
items: Int,
|
||||||
offset: Int
|
offset: Int
|
||||||
): Call<List<Item>> =
|
): Call<List<Item>> =
|
||||||
service.getItems(type, tag, sourceId, search, userName, password, items, offset)
|
service.getItems(type, tag, sourceId, search, userName, password, items, offset)
|
||||||
|
|
||||||
fun markItem(itemId: String): Call<SuccessResponse> =
|
fun markItem(itemId: String): Call<SuccessResponse> =
|
||||||
service.markAsRead(itemId, userName, password)
|
service.markAsRead(itemId, userName, password)
|
||||||
|
|
||||||
fun unmarkItem(itemId: String): Call<SuccessResponse> =
|
fun unmarkItem(itemId: String): Call<SuccessResponse> =
|
||||||
service.unmarkAsRead(itemId, userName, password)
|
service.unmarkAsRead(itemId, userName, password)
|
||||||
|
|
||||||
fun readAll(ids: List<String>): Call<SuccessResponse> =
|
fun readAll(ids: List<String>): Call<SuccessResponse> =
|
||||||
service.markAllAsRead(ids, userName, password)
|
service.markAllAsRead(ids, userName, password)
|
||||||
|
|
||||||
fun starrItem(itemId: String): Call<SuccessResponse> =
|
fun starrItem(itemId: String): Call<SuccessResponse> =
|
||||||
service.starr(itemId, userName, password)
|
service.starr(itemId, userName, password)
|
||||||
|
|
||||||
fun unstarrItem(itemId: String): Call<SuccessResponse> =
|
fun unstarrItem(itemId: String): Call<SuccessResponse> =
|
||||||
service.unstarr(itemId, userName, password)
|
service.unstarr(itemId, userName, password)
|
||||||
|
|
||||||
val stats: Call<Stats>
|
val stats: Call<Stats>
|
||||||
get() = service.stats(userName, password)
|
get() = service.stats(userName, password)
|
||||||
@ -157,23 +163,23 @@ class SelfossApi(
|
|||||||
get() = service.tags(userName, password)
|
get() = service.tags(userName, password)
|
||||||
|
|
||||||
fun update(): Call<String> =
|
fun update(): Call<String> =
|
||||||
service.update(userName, password)
|
service.update(userName, password)
|
||||||
|
|
||||||
val sources: Call<List<Sources>>
|
val sources: Call<List<Source>>
|
||||||
get() = service.sources(userName, password)
|
get() = service.sources(userName, password)
|
||||||
|
|
||||||
fun deleteSource(id: String): Call<SuccessResponse> =
|
fun deleteSource(id: String): Call<SuccessResponse> =
|
||||||
service.deleteSource(id, userName, password)
|
service.deleteSource(id, userName, password)
|
||||||
|
|
||||||
fun spouts(): Call<Map<String, Spout>> =
|
fun spouts(): Call<Map<String, Spout>> =
|
||||||
service.spouts(userName, password)
|
service.spouts(userName, password)
|
||||||
|
|
||||||
fun createSource(
|
fun createSource(
|
||||||
title: String,
|
title: String,
|
||||||
url: String,
|
url: String,
|
||||||
spout: String,
|
spout: String,
|
||||||
tags: String,
|
tags: String,
|
||||||
filter: String
|
filter: String
|
||||||
): Call<SuccessResponse> =
|
): Call<SuccessResponse> =
|
||||||
service.createSource(title, url, spout, tags, filter, userName, password)
|
service.createSource(title, url, spout, tags, filter, userName, password)
|
||||||
}
|
}
|
||||||
|
@ -9,21 +9,21 @@ import apps.amine.bou.readerforselfoss.utils.Config
|
|||||||
import apps.amine.bou.readerforselfoss.utils.isEmptyOrNullOrNullString
|
import apps.amine.bou.readerforselfoss.utils.isEmptyOrNullOrNullString
|
||||||
import com.google.gson.annotations.SerializedName
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
private fun constructUrl(config: Config?, path: String, file: String): String {
|
private fun constructUrl(config: Config?, path: String, file: String?): String {
|
||||||
val baseUriBuilder = Uri.parse(config!!.baseUrl).buildUpon()
|
|
||||||
baseUriBuilder.appendPath(path).appendPath(file)
|
|
||||||
|
|
||||||
return if (file.isEmptyOrNullOrNullString()) {
|
return if (file.isEmptyOrNullOrNullString()) {
|
||||||
""
|
""
|
||||||
} else {
|
} else {
|
||||||
|
val baseUriBuilder = Uri.parse(config!!.baseUrl).buildUpon()
|
||||||
|
baseUriBuilder.appendPath(path).appendPath(file)
|
||||||
|
|
||||||
baseUriBuilder.toString()
|
baseUriBuilder.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Tag(
|
data class Tag(
|
||||||
@SerializedName("tag") val tag: String,
|
@SerializedName("tag") val tag: String,
|
||||||
@SerializedName("color") val color: String,
|
@SerializedName("color") val color: String,
|
||||||
@SerializedName("unread") val unread: Int
|
@SerializedName("unread") val unread: Int
|
||||||
)
|
)
|
||||||
|
|
||||||
class SuccessResponse(@SerializedName("success") val success: Boolean) {
|
class SuccessResponse(@SerializedName("success") val success: Boolean) {
|
||||||
@ -32,23 +32,23 @@ class SuccessResponse(@SerializedName("success") val success: Boolean) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Stats(
|
class Stats(
|
||||||
@SerializedName("total") val total: Int,
|
@SerializedName("total") val total: Int,
|
||||||
@SerializedName("unread") val unread: Int,
|
@SerializedName("unread") val unread: Int,
|
||||||
@SerializedName("starred") val starred: Int
|
@SerializedName("starred") val starred: Int
|
||||||
)
|
)
|
||||||
|
|
||||||
data class Spout(
|
data class Spout(
|
||||||
@SerializedName("name") val name: String,
|
@SerializedName("name") val name: String,
|
||||||
@SerializedName("description") val description: String
|
@SerializedName("description") val description: String
|
||||||
)
|
)
|
||||||
|
|
||||||
data class Sources(
|
data class Source(
|
||||||
@SerializedName("id") val id: String,
|
@SerializedName("id") val id: String,
|
||||||
@SerializedName("title") val title: String,
|
@SerializedName("title") val title: String,
|
||||||
@SerializedName("tags") val tags: String,
|
@SerializedName("tags") val tags: SelfossTagType,
|
||||||
@SerializedName("spout") val spout: String,
|
@SerializedName("spout") val spout: String,
|
||||||
@SerializedName("error") val error: String,
|
@SerializedName("error") val error: String,
|
||||||
@SerializedName("icon") val icon: String
|
@SerializedName("icon") val icon: String
|
||||||
) {
|
) {
|
||||||
var config: Config? = null
|
var config: Config? = null
|
||||||
|
|
||||||
@ -61,16 +61,17 @@ data class Sources(
|
|||||||
}
|
}
|
||||||
|
|
||||||
data class Item(
|
data class Item(
|
||||||
@SerializedName("id") val id: String,
|
@SerializedName("id") val id: String,
|
||||||
@SerializedName("datetime") val datetime: String,
|
@SerializedName("datetime") val datetime: String,
|
||||||
@SerializedName("title") val title: String,
|
@SerializedName("title") val title: String,
|
||||||
@SerializedName("content") val content: String,
|
@SerializedName("content") val content: String,
|
||||||
@SerializedName("unread") val unread: Boolean,
|
@SerializedName("unread") val unread: Boolean,
|
||||||
@SerializedName("starred") val starred: Boolean,
|
@SerializedName("starred") var starred: Boolean,
|
||||||
@SerializedName("thumbnail") val thumbnail: String,
|
@SerializedName("thumbnail") val thumbnail: String,
|
||||||
@SerializedName("icon") val icon: String,
|
@SerializedName("icon") val icon: String,
|
||||||
@SerializedName("link") val link: String,
|
@SerializedName("link") val link: String,
|
||||||
@SerializedName("sourcetitle") val sourcetitle: String
|
@SerializedName("sourcetitle") val sourcetitle: String,
|
||||||
|
@SerializedName("tags") val tags: SelfossTagType
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
|
|
||||||
var config: Config? = null
|
var config: Config? = null
|
||||||
@ -83,16 +84,17 @@ data class Item(
|
|||||||
}
|
}
|
||||||
|
|
||||||
constructor(source: Parcel) : this(
|
constructor(source: Parcel) : this(
|
||||||
id = source.readString(),
|
id = source.readString(),
|
||||||
datetime = source.readString(),
|
datetime = source.readString(),
|
||||||
title = source.readString(),
|
title = source.readString(),
|
||||||
content = source.readString(),
|
content = source.readString(),
|
||||||
unread = 0.toByte() != source.readByte(),
|
unread = 0.toByte() != source.readByte(),
|
||||||
starred = 0.toByte() != source.readByte(),
|
starred = 0.toByte() != source.readByte(),
|
||||||
thumbnail = source.readString(),
|
thumbnail = source.readString(),
|
||||||
icon = source.readString(),
|
icon = source.readString(),
|
||||||
link = source.readString(),
|
link = source.readString(),
|
||||||
sourcetitle = source.readString()
|
sourcetitle = source.readString(),
|
||||||
|
tags = source.readParcelable(ClassLoader.getSystemClassLoader())
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun describeContents() = 0
|
override fun describeContents() = 0
|
||||||
@ -108,6 +110,7 @@ data class Item(
|
|||||||
dest.writeString(icon)
|
dest.writeString(icon)
|
||||||
dest.writeString(link)
|
dest.writeString(link)
|
||||||
dest.writeString(sourcetitle)
|
dest.writeString(sourcetitle)
|
||||||
|
dest.writeParcelable(tags, flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getIcon(app: Context): String {
|
fun getIcon(app: Context): String {
|
||||||
@ -127,15 +130,16 @@ data class Item(
|
|||||||
// TODO: maybe find a better way to handle these kind of urls
|
// TODO: maybe find a better way to handle these kind of urls
|
||||||
fun getLinkDecoded(): String {
|
fun getLinkDecoded(): String {
|
||||||
var stringUrl: String
|
var stringUrl: String
|
||||||
stringUrl = if (link.startsWith("http://news.google.com/news/") || link.startsWith("https://news.google.com/news/")) {
|
stringUrl =
|
||||||
if (link.contains("&url=")) {
|
if (link.startsWith("http://news.google.com/news/") || link.startsWith("https://news.google.com/news/")) {
|
||||||
link.substringAfter("&url=")
|
if (link.contains("&url=")) {
|
||||||
} else {
|
link.substringAfter("&url=")
|
||||||
this.link.replace("&", "&")
|
} else {
|
||||||
}
|
this.link.replace("&", "&")
|
||||||
} else {
|
}
|
||||||
this.link.replace("&", "&")
|
} else {
|
||||||
}
|
this.link.replace("&", "&")
|
||||||
|
}
|
||||||
|
|
||||||
// handle :443 => https
|
// handle :443 => https
|
||||||
if (stringUrl.contains(":443")) {
|
if (stringUrl.contains(":443")) {
|
||||||
@ -144,9 +148,32 @@ data class Item(
|
|||||||
|
|
||||||
// handle url not starting with http
|
// handle url not starting with http
|
||||||
if (stringUrl.startsWith("//")) {
|
if (stringUrl.startsWith("//")) {
|
||||||
stringUrl = "http:" + stringUrl
|
stringUrl = "http:$stringUrl"
|
||||||
}
|
}
|
||||||
|
|
||||||
return stringUrl
|
return stringUrl
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class SelfossTagType(val tags: String) : Parcelable {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmField val CREATOR: Parcelable.Creator<SelfossTagType> =
|
||||||
|
object : Parcelable.Creator<SelfossTagType> {
|
||||||
|
override fun createFromParcel(source: Parcel): SelfossTagType =
|
||||||
|
SelfossTagType(source)
|
||||||
|
|
||||||
|
override fun newArray(size: Int): Array<SelfossTagType?> = arrayOfNulls(size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(source: Parcel) : this(
|
||||||
|
tags = source.readString()
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun describeContents() = 0
|
||||||
|
|
||||||
|
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||||
|
dest.writeString(tags)
|
||||||
|
}
|
||||||
}
|
}
|
@ -17,102 +17,108 @@ internal interface SelfossService {
|
|||||||
|
|
||||||
@GET("items")
|
@GET("items")
|
||||||
fun getItems(
|
fun getItems(
|
||||||
@Query("type") type: String,
|
@Query("type") type: String,
|
||||||
@Query("tag") tag: String?,
|
@Query("tag") tag: String?,
|
||||||
@Query("source") source: Long?,
|
@Query("source") source: Long?,
|
||||||
@Query("search") search: String?,
|
@Query("search") search: String?,
|
||||||
@Query("username") username: String,
|
@Query("username") username: String,
|
||||||
@Query("password") password: String,
|
@Query("password") password: String,
|
||||||
@Query("items") items: Int,
|
@Query("items") items: Int,
|
||||||
@Query("offset") offset: Int
|
@Query("offset") offset: Int
|
||||||
|
): Call<List<Item>>
|
||||||
|
|
||||||
|
@GET("items")
|
||||||
|
fun allItems(
|
||||||
|
@Query("username") username: String,
|
||||||
|
@Query("password") password: String
|
||||||
): Call<List<Item>>
|
): Call<List<Item>>
|
||||||
|
|
||||||
@Headers("Content-Type: application/x-www-form-urlencoded")
|
@Headers("Content-Type: application/x-www-form-urlencoded")
|
||||||
@POST("mark/{id}")
|
@POST("mark/{id}")
|
||||||
fun markAsRead(
|
fun markAsRead(
|
||||||
@Path("id") id: String,
|
@Path("id") id: String,
|
||||||
@Query("username") username: String,
|
@Query("username") username: String,
|
||||||
@Query("password") password: String
|
@Query("password") password: String
|
||||||
): Call<SuccessResponse>
|
): Call<SuccessResponse>
|
||||||
|
|
||||||
@Headers("Content-Type: application/x-www-form-urlencoded")
|
@Headers("Content-Type: application/x-www-form-urlencoded")
|
||||||
@POST("unmark/{id}")
|
@POST("unmark/{id}")
|
||||||
fun unmarkAsRead(
|
fun unmarkAsRead(
|
||||||
@Path("id") id: String,
|
@Path("id") id: String,
|
||||||
@Query("username") username: String,
|
@Query("username") username: String,
|
||||||
@Query("password") password: String
|
@Query("password") password: String
|
||||||
): Call<SuccessResponse>
|
): Call<SuccessResponse>
|
||||||
|
|
||||||
@FormUrlEncoded
|
@FormUrlEncoded
|
||||||
@POST("mark")
|
@POST("mark")
|
||||||
fun markAllAsRead(
|
fun markAllAsRead(
|
||||||
@Field("ids[]") ids: List<String>,
|
@Field("ids[]") ids: List<String>,
|
||||||
@Query("username") username: String,
|
@Query("username") username: String,
|
||||||
@Query("password") password: String
|
@Query("password") password: String
|
||||||
): Call<SuccessResponse>
|
): Call<SuccessResponse>
|
||||||
|
|
||||||
@Headers("Content-Type: application/x-www-form-urlencoded")
|
@Headers("Content-Type: application/x-www-form-urlencoded")
|
||||||
@POST("starr/{id}")
|
@POST("starr/{id}")
|
||||||
fun starr(
|
fun starr(
|
||||||
@Path("id") id: String,
|
@Path("id") id: String,
|
||||||
@Query("username") username: String,
|
@Query("username") username: String,
|
||||||
@Query("password") password: String
|
@Query("password") password: String
|
||||||
): Call<SuccessResponse>
|
): Call<SuccessResponse>
|
||||||
|
|
||||||
@Headers("Content-Type: application/x-www-form-urlencoded")
|
@Headers("Content-Type: application/x-www-form-urlencoded")
|
||||||
@POST("unstarr/{id}")
|
@POST("unstarr/{id}")
|
||||||
fun unstarr(
|
fun unstarr(
|
||||||
@Path("id") id: String,
|
@Path("id") id: String,
|
||||||
@Query("username") username: String,
|
@Query("username") username: String,
|
||||||
@Query("password") password: String
|
@Query("password") password: String
|
||||||
): Call<SuccessResponse>
|
): Call<SuccessResponse>
|
||||||
|
|
||||||
@GET("stats")
|
@GET("stats")
|
||||||
fun stats(
|
fun stats(
|
||||||
@Query("username") username: String,
|
@Query("username") username: String,
|
||||||
@Query("password") password: String
|
@Query("password") password: String
|
||||||
): Call<Stats>
|
): Call<Stats>
|
||||||
|
|
||||||
@GET("tags")
|
@GET("tags")
|
||||||
fun tags(
|
fun tags(
|
||||||
@Query("username") username: String,
|
@Query("username") username: String,
|
||||||
@Query("password") password: String
|
@Query("password") password: String
|
||||||
): Call<List<Tag>>
|
): Call<List<Tag>>
|
||||||
|
|
||||||
@GET("update")
|
@GET("update")
|
||||||
fun update(
|
fun update(
|
||||||
@Query("username") username: String,
|
@Query("username") username: String,
|
||||||
@Query("password") password: String
|
@Query("password") password: String
|
||||||
): Call<String>
|
): Call<String>
|
||||||
|
|
||||||
@GET("sources/spouts")
|
@GET("sources/spouts")
|
||||||
fun spouts(
|
fun spouts(
|
||||||
@Query("username") username: String,
|
@Query("username") username: String,
|
||||||
@Query("password") password: String
|
@Query("password") password: String
|
||||||
): Call<Map<String, Spout>>
|
): Call<Map<String, Spout>>
|
||||||
|
|
||||||
@GET("sources/list")
|
@GET("sources/list")
|
||||||
fun sources(
|
fun sources(
|
||||||
@Query("username") username: String,
|
@Query("username") username: String,
|
||||||
@Query("password") password: String
|
@Query("password") password: String
|
||||||
): Call<List<Sources>>
|
): Call<List<Source>>
|
||||||
|
|
||||||
@DELETE("source/{id}")
|
@DELETE("source/{id}")
|
||||||
fun deleteSource(
|
fun deleteSource(
|
||||||
@Path("id") id: String,
|
@Path("id") id: String,
|
||||||
@Query("username") username: String,
|
@Query("username") username: String,
|
||||||
@Query("password") password: String
|
@Query("password") password: String
|
||||||
): Call<SuccessResponse>
|
): Call<SuccessResponse>
|
||||||
|
|
||||||
@FormUrlEncoded
|
@FormUrlEncoded
|
||||||
@POST("source")
|
@POST("source")
|
||||||
fun createSource(
|
fun createSource(
|
||||||
@Field("title") title: String,
|
@Field("title") title: String,
|
||||||
@Field("url") url: String,
|
@Field("url") url: String,
|
||||||
@Field("spout") spout: String,
|
@Field("spout") spout: String,
|
||||||
@Field("tags") tags: String,
|
@Field("tags") tags: String,
|
||||||
@Field("filter") filter: String,
|
@Field("filter") filter: String,
|
||||||
@Query("username") username: String,
|
@Query("username") username: String,
|
||||||
@Query("password") password: String
|
@Query("password") password: String
|
||||||
): Call<SuccessResponse>
|
): Call<SuccessResponse>
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
package apps.amine.bou.readerforselfoss.api.selfoss
|
||||||
|
|
||||||
|
import com.google.gson.JsonDeserializationContext
|
||||||
|
import com.google.gson.JsonDeserializer
|
||||||
|
import com.google.gson.JsonElement
|
||||||
|
import com.google.gson.JsonParseException
|
||||||
|
import java.lang.reflect.Type
|
||||||
|
|
||||||
|
internal class SelfossTagTypeTypeAdapter : JsonDeserializer<SelfossTagType> {
|
||||||
|
|
||||||
|
@Throws(JsonParseException::class)
|
||||||
|
override fun deserialize(
|
||||||
|
json: JsonElement,
|
||||||
|
typeOfT: Type,
|
||||||
|
context: JsonDeserializationContext
|
||||||
|
): SelfossTagType? =
|
||||||
|
if (json.isJsonArray) {
|
||||||
|
SelfossTagType(json.asJsonArray.joinToString(",") { it.toString() })
|
||||||
|
} else {
|
||||||
|
SelfossTagType(json.toString())
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,141 @@
|
|||||||
|
package apps.amine.bou.readerforselfoss.background
|
||||||
|
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.content.Context
|
||||||
|
import android.preference.PreferenceManager
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.app.NotificationCompat.PRIORITY_DEFAULT
|
||||||
|
import androidx.core.app.NotificationCompat.PRIORITY_LOW
|
||||||
|
import androidx.room.Room
|
||||||
|
import androidx.work.Worker
|
||||||
|
import androidx.work.WorkerParameters
|
||||||
|
import 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.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.utils.Config
|
||||||
|
import apps.amine.bou.readerforselfoss.utils.maybeHandleSilentException
|
||||||
|
import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible
|
||||||
|
import apps.amine.bou.readerforselfoss.utils.persistence.toEntity
|
||||||
|
import org.acra.ACRA
|
||||||
|
import retrofit2.Call
|
||||||
|
import retrofit2.Callback
|
||||||
|
import retrofit2.Response
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.concurrent.schedule
|
||||||
|
import kotlin.concurrent.thread
|
||||||
|
|
||||||
|
class LoadingWorker(val context: Context, params: WorkerParameters) : Worker(context, params) {
|
||||||
|
lateinit var db: AppDatabase
|
||||||
|
|
||||||
|
override fun doWork(): Result {
|
||||||
|
if (context.isNetworkAccessible(null)) {
|
||||||
|
|
||||||
|
val notificationManager =
|
||||||
|
applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
|
||||||
|
val notification = NotificationCompat.Builder(applicationContext, Config.syncChannelId)
|
||||||
|
.setContentTitle(context.getString(R.string.loading_notification_title))
|
||||||
|
.setContentText(context.getString(R.string.loading_notification_text))
|
||||||
|
.setOngoing(true)
|
||||||
|
.setPriority(PRIORITY_LOW)
|
||||||
|
.setChannelId(Config.syncChannelId)
|
||||||
|
.setSmallIcon(R.drawable.ic_cloud_download)
|
||||||
|
|
||||||
|
notificationManager.notify(1, notification.build())
|
||||||
|
|
||||||
|
val settings =
|
||||||
|
this.context.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
|
||||||
|
val sharedPref = PreferenceManager.getDefaultSharedPreferences(this.context)
|
||||||
|
val shouldLogEverything = sharedPref.getBoolean("should_log_everything", false)
|
||||||
|
val notifyNewItems = sharedPref.getBoolean("notify_new_items", false)
|
||||||
|
|
||||||
|
db = Room.databaseBuilder(
|
||||||
|
applicationContext,
|
||||||
|
AppDatabase::class.java, "selfoss-database"
|
||||||
|
).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).build()
|
||||||
|
|
||||||
|
val api = SelfossApi(
|
||||||
|
this.context,
|
||||||
|
null,
|
||||||
|
settings.getBoolean("isSelfSignedCert", false),
|
||||||
|
shouldLogEverything
|
||||||
|
)
|
||||||
|
|
||||||
|
api.allItems().enqueue(object : Callback<List<Item>> {
|
||||||
|
override fun onFailure(call: Call<List<Item>>, t: Throwable) {
|
||||||
|
Timer("", false).schedule(4000) {
|
||||||
|
notificationManager.cancel(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResponse(
|
||||||
|
call: Call<List<Item>>,
|
||||||
|
response: Response<List<Item>>
|
||||||
|
) {
|
||||||
|
thread {
|
||||||
|
if (response.body() != null) {
|
||||||
|
val apiItems = (response.body() as ArrayList<Item>)
|
||||||
|
db.itemsDao().deleteAllItems()
|
||||||
|
db.itemsDao()
|
||||||
|
.insertAllItems(*(apiItems.map { it.toEntity() }).toTypedArray())
|
||||||
|
|
||||||
|
val newSize = apiItems.filter { it.unread }.size
|
||||||
|
if (notifyNewItems && newSize > 0) {
|
||||||
|
val newItemsNotification = NotificationCompat.Builder(applicationContext, Config.newItemsChannelId)
|
||||||
|
.setContentTitle(context.getString(R.string.new_items_notification_title))
|
||||||
|
.setContentText(context.getString(R.string.new_items_notification_text, newSize))
|
||||||
|
.setPriority(PRIORITY_DEFAULT)
|
||||||
|
.setChannelId(Config.newItemsChannelId)
|
||||||
|
.setSmallIcon(R.drawable.ic_fiber_new_black_24dp)
|
||||||
|
|
||||||
|
Timer("", false).schedule(4000) {
|
||||||
|
notificationManager.notify(2, newItemsNotification.build())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Timer("", false).schedule(4000) {
|
||||||
|
notificationManager.cancel(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
thread {
|
||||||
|
val actions = db.actionsDao().actions()
|
||||||
|
|
||||||
|
actions.forEach { action ->
|
||||||
|
when {
|
||||||
|
action.read -> doAndReportOnFail(api.markItem(action.articleId), action)
|
||||||
|
action.unread -> doAndReportOnFail(api.unmarkItem(action.articleId), action)
|
||||||
|
action.starred -> doAndReportOnFail(api.starrItem(action.articleId), action)
|
||||||
|
action.unstarred -> doAndReportOnFail(
|
||||||
|
api.unstarrItem(action.articleId),
|
||||||
|
action
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Result.SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <T> doAndReportOnFail(call: Call<T>, action: ActionEntity) {
|
||||||
|
call.enqueue(object : Callback<T> {
|
||||||
|
override fun onResponse(
|
||||||
|
call: Call<T>,
|
||||||
|
response: Response<T>
|
||||||
|
) {
|
||||||
|
thread {
|
||||||
|
db.actionsDao().delete(action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(call: Call<T>, t: Throwable) {
|
||||||
|
ACRA.getErrorReporter().maybeHandleSilentException(t, context)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -1,44 +1,60 @@
|
|||||||
package apps.amine.bou.readerforselfoss.fragments
|
package apps.amine.bou.readerforselfoss.fragments
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
import android.content.res.ColorStateList
|
||||||
|
import android.graphics.drawable.ColorDrawable
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.preference.PreferenceManager
|
import android.preference.PreferenceManager
|
||||||
import android.support.customtabs.CustomTabsIntent
|
import androidx.browser.customtabs.CustomTabsIntent
|
||||||
import android.support.design.widget.FloatingActionButton
|
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
import android.support.v4.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import android.support.v4.widget.NestedScrollView
|
import androidx.core.content.ContextCompat
|
||||||
import android.text.Html
|
import androidx.core.widget.NestedScrollView
|
||||||
import android.text.method.LinkMovementMethod
|
|
||||||
import android.util.TypedValue
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.webkit.WebSettings
|
import android.webkit.WebSettings
|
||||||
import apps.amine.bou.readerforselfoss.BuildConfig
|
import androidx.room.Room
|
||||||
import apps.amine.bou.readerforselfoss.R
|
import apps.amine.bou.readerforselfoss.R
|
||||||
import apps.amine.bou.readerforselfoss.api.mercury.MercuryApi
|
import apps.amine.bou.readerforselfoss.api.mercury.MercuryApi
|
||||||
import apps.amine.bou.readerforselfoss.api.mercury.ParsedContent
|
import apps.amine.bou.readerforselfoss.api.mercury.ParsedContent
|
||||||
import apps.amine.bou.readerforselfoss.api.selfoss.Item
|
import apps.amine.bou.readerforselfoss.api.selfoss.Item
|
||||||
|
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
|
||||||
|
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
|
||||||
|
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.utils.Config
|
||||||
import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent
|
import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent
|
||||||
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
|
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
|
||||||
import apps.amine.bou.readerforselfoss.utils.isEmptyOrNullOrNullString
|
import apps.amine.bou.readerforselfoss.utils.isEmptyOrNullOrNullString
|
||||||
|
import apps.amine.bou.readerforselfoss.utils.maybeHandleSilentException
|
||||||
|
import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible
|
||||||
import apps.amine.bou.readerforselfoss.utils.openItemUrl
|
import apps.amine.bou.readerforselfoss.utils.openItemUrl
|
||||||
import apps.amine.bou.readerforselfoss.utils.shareLink
|
import apps.amine.bou.readerforselfoss.utils.shareLink
|
||||||
import apps.amine.bou.readerforselfoss.utils.sourceAndDateText
|
import apps.amine.bou.readerforselfoss.utils.sourceAndDateText
|
||||||
|
import apps.amine.bou.readerforselfoss.utils.succeeded
|
||||||
|
import apps.amine.bou.readerforselfoss.utils.toPx
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.request.RequestOptions
|
import com.bumptech.glide.request.RequestOptions
|
||||||
import com.crashlytics.android.Crashlytics
|
|
||||||
import com.github.rubensousa.floatingtoolbar.FloatingToolbar
|
import com.github.rubensousa.floatingtoolbar.FloatingToolbar
|
||||||
import kotlinx.android.synthetic.main.fragment_article.view.*
|
import kotlinx.android.synthetic.main.fragment_article.view.*
|
||||||
import org.sufficientlysecure.htmltextview.HtmlHttpImageGetter
|
import org.acra.ACRA
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
import retrofit2.Callback
|
import retrofit2.Callback
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
|
import java.net.MalformedURLException
|
||||||
|
import java.net.URL
|
||||||
|
import kotlin.concurrent.thread
|
||||||
|
|
||||||
class ArticleFragment : Fragment() {
|
class ArticleFragment : Fragment() {
|
||||||
private lateinit var pageNumber: Number
|
private lateinit var pageNumber: Number
|
||||||
|
private var fontSize: Int = 14
|
||||||
private lateinit var allItems: ArrayList<Item>
|
private lateinit var allItems: ArrayList<Item>
|
||||||
private lateinit var mCustomTabActivityHelper: CustomTabActivityHelper
|
private lateinit var mCustomTabActivityHelper: CustomTabActivityHelper
|
||||||
private lateinit var url: String
|
private lateinit var url: String
|
||||||
@ -46,9 +62,10 @@ class ArticleFragment : Fragment() {
|
|||||||
private lateinit var contentSource: String
|
private lateinit var contentSource: String
|
||||||
private lateinit var contentImage: String
|
private lateinit var contentImage: String
|
||||||
private lateinit var contentTitle: String
|
private lateinit var contentTitle: String
|
||||||
|
private lateinit var editor: SharedPreferences.Editor
|
||||||
private lateinit var fab: FloatingActionButton
|
private lateinit var fab: FloatingActionButton
|
||||||
|
private lateinit var appColors: AppColors
|
||||||
private var useWebview: Boolean = false
|
private lateinit var db: AppDatabase
|
||||||
|
|
||||||
override fun onStop() {
|
override fun onStop() {
|
||||||
super.onStop()
|
super.onStop()
|
||||||
@ -56,21 +73,29 @@ class ArticleFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
appColors = AppColors(activity!!)
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
pageNumber = arguments!!.getInt(ARG_POSITION)
|
pageNumber = arguments!!.getInt(ARG_POSITION)
|
||||||
allItems = arguments!!.getParcelableArrayList(ARG_ITEMS)
|
allItems = arguments!!.getParcelableArrayList(ARG_ITEMS)
|
||||||
useWebview = arguments!!.getBoolean(ARG_WEBVIEW)
|
|
||||||
|
db = Room.databaseBuilder(
|
||||||
|
context!!,
|
||||||
|
AppDatabase::class.java, "selfoss-database"
|
||||||
|
).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).build()
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var rootView: ViewGroup
|
private lateinit var rootView: ViewGroup
|
||||||
|
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View? {
|
||||||
rootView = inflater
|
rootView = inflater
|
||||||
.inflate(R.layout.fragment_article, container, false) as ViewGroup
|
.inflate(R.layout.fragment_article, container, false) as ViewGroup
|
||||||
|
|
||||||
url = allItems[pageNumber.toInt()].getLinkDecoded()
|
url = allItems[pageNumber.toInt()].getLinkDecoded()
|
||||||
contentText = allItems[pageNumber.toInt()].content
|
contentText = allItems[pageNumber.toInt()].content
|
||||||
@ -78,230 +103,361 @@ class ArticleFragment : Fragment() {
|
|||||||
contentImage = allItems[pageNumber.toInt()].getThumbnail(activity!!)
|
contentImage = allItems[pageNumber.toInt()].getThumbnail(activity!!)
|
||||||
contentSource = allItems[pageNumber.toInt()].sourceAndDateText()
|
contentSource = allItems[pageNumber.toInt()].sourceAndDateText()
|
||||||
|
|
||||||
|
val prefs = PreferenceManager.getDefaultSharedPreferences(activity)
|
||||||
|
editor = prefs.edit()
|
||||||
|
fontSize = prefs.getString("reader_font_size", "14").toInt()
|
||||||
|
|
||||||
|
val settings = activity!!.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
|
||||||
|
val debugReadingItems = prefs.getBoolean("read_debug", false)
|
||||||
|
|
||||||
|
val api = SelfossApi(
|
||||||
|
context!!,
|
||||||
|
activity!!,
|
||||||
|
settings.getBoolean("isSelfSignedCert", false),
|
||||||
|
prefs.getBoolean("should_log_everything", false)
|
||||||
|
)
|
||||||
|
|
||||||
fab = rootView.fab
|
fab = rootView.fab
|
||||||
val mFloatingToolbar: FloatingToolbar = rootView.floatingToolbar
|
|
||||||
mFloatingToolbar.attachFab(fab)
|
fab.backgroundTintList = ColorStateList.valueOf(appColors.colorAccent)
|
||||||
|
|
||||||
|
fab.rippleColor = appColors.colorAccentDark
|
||||||
|
|
||||||
|
val floatingToolbar: FloatingToolbar = rootView.floatingToolbar
|
||||||
|
floatingToolbar.attachFab(fab)
|
||||||
|
|
||||||
|
floatingToolbar.background = ColorDrawable(appColors.colorAccent)
|
||||||
|
|
||||||
val customTabsIntent = activity!!.buildCustomTabsIntent()
|
val customTabsIntent = activity!!.buildCustomTabsIntent()
|
||||||
mCustomTabActivityHelper = CustomTabActivityHelper()
|
mCustomTabActivityHelper = CustomTabActivityHelper()
|
||||||
mCustomTabActivityHelper.bindCustomTabsService(activity)
|
mCustomTabActivityHelper.bindCustomTabsService(activity)
|
||||||
|
|
||||||
val prefs = PreferenceManager.getDefaultSharedPreferences(activity)
|
|
||||||
|
|
||||||
mFloatingToolbar.setClickListener(
|
floatingToolbar.setClickListener(
|
||||||
object : FloatingToolbar.ItemClickListener {
|
object : FloatingToolbar.ItemClickListener {
|
||||||
override fun onItemClick(item: MenuItem) {
|
override fun onItemClick(item: MenuItem) {
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
R.id.more_action -> getContentFromMercury(customTabsIntent, prefs)
|
R.id.more_action -> getContentFromMercury(customTabsIntent, prefs)
|
||||||
R.id.share_action -> activity!!.shareLink(url)
|
R.id.share_action -> activity!!.shareLink(url)
|
||||||
R.id.open_action -> activity!!.openItemUrl(
|
R.id.open_action -> activity!!.openItemUrl(
|
||||||
allItems,
|
allItems,
|
||||||
pageNumber.toInt(),
|
pageNumber.toInt(),
|
||||||
url,
|
url,
|
||||||
customTabsIntent,
|
customTabsIntent,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
activity!!
|
activity!!
|
||||||
|
)
|
||||||
|
R.id.unread_action -> if ((context != null && context!!.isNetworkAccessible(null)) || context == null) {
|
||||||
|
api.unmarkItem(allItems[pageNumber.toInt()].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), activity!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(
|
||||||
|
call: Call<SuccessResponse>,
|
||||||
|
t: Throwable
|
||||||
|
) {
|
||||||
|
if (debugReadingItems) {
|
||||||
|
ACRA.getErrorReporter().maybeHandleSilentException(t, activity!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
else -> Unit
|
} else {
|
||||||
|
thread {
|
||||||
|
db.actionsDao().insertAllActions(ActionEntity(allItems[pageNumber.toInt()].id, false, true, false, false))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
else -> Unit
|
||||||
|
|
||||||
override fun onItemLongClick(item: MenuItem?) {
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onItemLongClick(item: MenuItem?) {
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
rootView.source.text = contentSource
|
||||||
|
|
||||||
if (contentText.isEmptyOrNullOrNullString()) {
|
if (contentText.isEmptyOrNullOrNullString()) {
|
||||||
getContentFromMercury(customTabsIntent, prefs)
|
getContentFromMercury(customTabsIntent, prefs)
|
||||||
} else {
|
} else {
|
||||||
rootView.source.text = contentSource
|
|
||||||
rootView.titleView.text = contentTitle
|
rootView.titleView.text = contentTitle
|
||||||
|
|
||||||
if (!useWebview) {
|
htmlToWebview(contentText, prefs)
|
||||||
htmlToTextview(contentText, customTabsIntent, prefs)
|
|
||||||
} else {
|
|
||||||
htmlToWebview(contentText)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!contentImage.isEmptyOrNullOrNullString()) {
|
if (!contentImage.isEmptyOrNullOrNullString() && context != null) {
|
||||||
rootView.imageView.visibility = View.VISIBLE
|
rootView.imageView.visibility = View.VISIBLE
|
||||||
Glide
|
Glide
|
||||||
.with(activity!!.baseContext)
|
.with(context!!)
|
||||||
.asBitmap()
|
.asBitmap()
|
||||||
.load(contentImage)
|
.load(contentImage)
|
||||||
.apply(RequestOptions.fitCenterTransform())
|
.apply(RequestOptions.fitCenterTransform())
|
||||||
.into(rootView.imageView)
|
.into(rootView.imageView)
|
||||||
} else {
|
} else {
|
||||||
rootView.imageView.visibility = View.GONE
|
rootView.imageView.visibility = View.GONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rootView.nestedScrollView.setOnScrollChangeListener(
|
rootView.nestedScrollView.setOnScrollChangeListener(
|
||||||
NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
|
NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
|
||||||
if (scrollY > oldScrollY) {
|
if (scrollY > oldScrollY) {
|
||||||
fab.hide()
|
fab.hide()
|
||||||
} else {
|
} else {
|
||||||
if (mFloatingToolbar.isShowing) mFloatingToolbar.hide() else fab.show()
|
if (floatingToolbar.isShowing) floatingToolbar.hide() else fab.show()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!useWebview) {
|
|
||||||
rootView.content.movementMethod = LinkMovementMethod.getInstance()
|
|
||||||
}
|
|
||||||
|
|
||||||
return rootView
|
return rootView
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getContentFromMercury(
|
private fun getContentFromMercury(
|
||||||
customTabsIntent: CustomTabsIntent,
|
customTabsIntent: CustomTabsIntent,
|
||||||
prefs: SharedPreferences
|
prefs: SharedPreferences
|
||||||
) {
|
) {
|
||||||
rootView.progressBar.visibility = View.VISIBLE
|
if ((context != null && context!!.isNetworkAccessible(null)) || context == null) {
|
||||||
val parser = MercuryApi(
|
rootView.progressBar.visibility = View.VISIBLE
|
||||||
BuildConfig.MERCURY_KEY,
|
val parser = MercuryApi(
|
||||||
prefs.getBoolean("should_log_everything", false)
|
prefs.getBoolean("should_log_everything", false)
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.parseUrl(url).enqueue(
|
parser.parseUrl(url).enqueue(
|
||||||
object : Callback<ParsedContent> {
|
object : Callback<ParsedContent> {
|
||||||
override fun onResponse(
|
override fun onResponse(
|
||||||
call: Call<ParsedContent>,
|
call: Call<ParsedContent>,
|
||||||
response: Response<ParsedContent>
|
response: Response<ParsedContent>
|
||||||
) {
|
) {
|
||||||
if (response.body() != null && response.body()!!.content != null && response.body()!!.content.isNotEmpty()) {
|
// TODO: clean all the following after finding the mercury content issue
|
||||||
rootView.source.text = response.body()!!.domain
|
try {
|
||||||
rootView.titleView.text = response.body()!!.title
|
if (response.body() != null && response.body()!!.content != null && !response.body()!!.content.isNullOrEmpty()) {
|
||||||
url = response.body()!!.url
|
try {
|
||||||
|
rootView.titleView.text = response.body()!!.title
|
||||||
|
try {
|
||||||
|
// Note: Mercury may return relative urls... If it does the url val will not be changed.
|
||||||
|
URL(response.body()!!.url)
|
||||||
|
url = response.body()!!.url
|
||||||
|
} catch (e: MalformedURLException) {
|
||||||
|
ACRA.getErrorReporter().maybeHandleSilentException(e, activity!!)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
if (context != null) {
|
||||||
|
ACRA.getErrorReporter().maybeHandleSilentException(e, context!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (response.body()!!.content != null && !response.body()!!.content.isEmpty()) {
|
try {
|
||||||
if (!useWebview) {
|
htmlToWebview(response.body()!!.content.orEmpty(), prefs)
|
||||||
htmlToTextview(
|
} catch (e: Exception) {
|
||||||
response.body()!!.content,
|
if (context != null) {
|
||||||
customTabsIntent,
|
ACRA.getErrorReporter().maybeHandleSilentException(e, context!!)
|
||||||
prefs
|
}
|
||||||
)
|
}
|
||||||
} else {
|
|
||||||
htmlToWebview(response.body()!!.content)
|
try {
|
||||||
|
if (response.body()!!.lead_image_url != null && !response.body()!!.lead_image_url.isNullOrEmpty() && context != null) {
|
||||||
|
rootView.imageView.visibility = View.VISIBLE
|
||||||
|
try {
|
||||||
|
Glide
|
||||||
|
.with(context!!)
|
||||||
|
.asBitmap()
|
||||||
|
.load(response.body()!!.lead_image_url)
|
||||||
|
.apply(RequestOptions.fitCenterTransform())
|
||||||
|
.into(rootView.imageView)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
ACRA.getErrorReporter().maybeHandleSilentException(e, context!!)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rootView.imageView.visibility = View.GONE
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
if (context != null) {
|
||||||
|
ACRA.getErrorReporter().maybeHandleSilentException(e, context!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
rootView.nestedScrollView.scrollTo(0, 0)
|
||||||
|
|
||||||
|
rootView.progressBar.visibility = View.GONE
|
||||||
|
} catch (e: Exception) {
|
||||||
|
if (context != null) {
|
||||||
|
ACRA.getErrorReporter().maybeHandleSilentException(e, context!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
openInBrowserAfterFailing(customTabsIntent)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
if (context != null) {
|
||||||
|
ACRA.getErrorReporter().maybeHandleSilentException(e, context!!)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
if (response.body()!!.lead_image_url != null && !response.body()!!.lead_image_url.isEmpty()) {
|
if (context != null) {
|
||||||
rootView.imageView.visibility = View.VISIBLE
|
ACRA.getErrorReporter().maybeHandleSilentException(e, context!!)
|
||||||
Glide
|
|
||||||
.with(activity!!.baseContext)
|
|
||||||
.asBitmap()
|
|
||||||
.load(response.body()!!.lead_image_url)
|
|
||||||
.apply(RequestOptions.fitCenterTransform())
|
|
||||||
.into(rootView.imageView)
|
|
||||||
} else {
|
|
||||||
rootView.imageView.visibility = View.GONE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rootView.nestedScrollView.scrollTo(0, 0)
|
|
||||||
|
|
||||||
rootView.progressBar.visibility = View.GONE
|
|
||||||
} else {
|
|
||||||
openInBrowserAfterFailing(customTabsIntent)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailure(
|
override fun onFailure(
|
||||||
call: Call<ParsedContent>,
|
call: Call<ParsedContent>,
|
||||||
t: Throwable
|
t: Throwable
|
||||||
) = openInBrowserAfterFailing(customTabsIntent)
|
) = openInBrowserAfterFailing(customTabsIntent)
|
||||||
}
|
}
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun htmlToTextview(
|
|
||||||
c: String,
|
|
||||||
customTabsIntent: CustomTabsIntent,
|
|
||||||
prefs: SharedPreferences
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
rootView.content.visibility = View.VISIBLE
|
|
||||||
rootView.content.text = Html.fromHtml(
|
|
||||||
c,
|
|
||||||
HtmlHttpImageGetter(rootView.content, null, true),
|
|
||||||
null
|
|
||||||
)
|
)
|
||||||
} 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 htmlToWebview(c: String) {
|
private fun htmlToWebview(c: String, prefs: SharedPreferences) {
|
||||||
val attrBackground = TypedValue()
|
val stringColor = String.format("#%06X", 0xFFFFFF and appColors.colorAccent)
|
||||||
activity!!.baseContext.theme.resolveAttribute(
|
|
||||||
android.R.attr.colorBackground,
|
|
||||||
attrBackground,
|
|
||||||
true
|
|
||||||
)
|
|
||||||
|
|
||||||
val defaultColor = TypedValue()
|
|
||||||
activity!!.baseContext.theme.resolveAttribute(
|
|
||||||
android.R.attr.colorAccent,
|
|
||||||
defaultColor,
|
|
||||||
true
|
|
||||||
)
|
|
||||||
val accentColor = fab.backgroundTintList?.defaultColor ?: defaultColor.data
|
|
||||||
val stringColor = String.format("#%06X", 0xFFFFFF and accentColor)
|
|
||||||
|
|
||||||
rootView.webcontent.visibility = View.VISIBLE
|
rootView.webcontent.visibility = View.VISIBLE
|
||||||
rootView.webcontent.setBackgroundColor(attrBackground.data)
|
val (textColor, backgroundColor) = if (appColors.isDarkTheme) {
|
||||||
|
if (context != null) {
|
||||||
|
rootView.webcontent.setBackgroundColor(
|
||||||
|
ContextCompat.getColor(
|
||||||
|
context!!,
|
||||||
|
R.color.dark_webview
|
||||||
|
)
|
||||||
|
)
|
||||||
|
Pair(ContextCompat.getColor(context!!, R.color.dark_webview_text), ContextCompat.getColor(context!!, R.color.light_webview_text))
|
||||||
|
} else {
|
||||||
|
Pair(null, null)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (context != null) {
|
||||||
|
rootView.webcontent.setBackgroundColor(
|
||||||
|
ContextCompat.getColor(
|
||||||
|
context!!,
|
||||||
|
R.color.light_webview
|
||||||
|
)
|
||||||
|
)
|
||||||
|
Pair(ContextCompat.getColor(context!!, R.color.light_webview_text), ContextCompat.getColor(context!!, R.color.dark_webview_text))
|
||||||
|
} else {
|
||||||
|
Pair(null, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val stringTextColor: String = if (textColor != null) {
|
||||||
|
String.format("#%06X", 0xFFFFFF and textColor)
|
||||||
|
} else {
|
||||||
|
"#000000"
|
||||||
|
}
|
||||||
|
|
||||||
|
val stringBackgroundColor = if (backgroundColor != null) {
|
||||||
|
String.format("#%06X", 0xFFFFFF and backgroundColor)
|
||||||
|
} else {
|
||||||
|
"#FFFFFF"
|
||||||
|
}
|
||||||
|
|
||||||
rootView.webcontent.settings.useWideViewPort = true
|
rootView.webcontent.settings.useWideViewPort = true
|
||||||
rootView.webcontent.settings.loadWithOverviewMode = true
|
rootView.webcontent.settings.loadWithOverviewMode = true
|
||||||
rootView.webcontent.settings.javaScriptEnabled = false
|
rootView.webcontent.settings.javaScriptEnabled = false
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||||
rootView.webcontent.settings.layoutAlgorithm = WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
|
rootView.webcontent.settings.layoutAlgorithm =
|
||||||
|
WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
|
||||||
} else {
|
} else {
|
||||||
rootView.webcontent.settings.layoutAlgorithm = WebSettings.LayoutAlgorithm.SINGLE_COLUMN
|
rootView.webcontent.settings.layoutAlgorithm = WebSettings.LayoutAlgorithm.SINGLE_COLUMN
|
||||||
}
|
}
|
||||||
|
|
||||||
rootView.webcontent.loadData(
|
var baseUrl: String? = null
|
||||||
"<style>img{display: inline-block;height: auto; width: 100%; max-width: 100%;} a{color: $stringColor}</style>$c",
|
|
||||||
"text/html; charset=utf-8",
|
try {
|
||||||
"utf-8"
|
val itemUrl = URL(url)
|
||||||
|
baseUrl = itemUrl.protocol + "://" + itemUrl.host
|
||||||
|
} catch (e: MalformedURLException) {
|
||||||
|
ACRA.getErrorReporter().maybeHandleSilentException(e, activity!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
rootView.webcontent.loadDataWithBaseURL(
|
||||||
|
baseUrl,
|
||||||
|
"""<html>
|
||||||
|
|<head>
|
||||||
|
| <style>
|
||||||
|
| img {
|
||||||
|
| display: inline-block;
|
||||||
|
| height: auto;
|
||||||
|
| width: 100%;
|
||||||
|
| max-width: 100%;
|
||||||
|
| }
|
||||||
|
| a {
|
||||||
|
| color: $stringColor !important;
|
||||||
|
| }
|
||||||
|
| *:not(a) {
|
||||||
|
| color: $stringTextColor;
|
||||||
|
| }
|
||||||
|
| * {
|
||||||
|
| font-size: ${fontSize.toPx}px;
|
||||||
|
| text-align: justify;
|
||||||
|
| word-break: break-word;
|
||||||
|
| overflow:hidden;
|
||||||
|
| }
|
||||||
|
| a, pre, code {
|
||||||
|
| text-align: left;
|
||||||
|
| }
|
||||||
|
| pre, code {
|
||||||
|
| white-space: pre-wrap;
|
||||||
|
| width:100%;
|
||||||
|
| background-color: $stringBackgroundColor;
|
||||||
|
| }
|
||||||
|
| </style>
|
||||||
|
|</head>
|
||||||
|
|<body>
|
||||||
|
| $c
|
||||||
|
|</body>""".trimMargin(),
|
||||||
|
"text/html",
|
||||||
|
"utf-8",
|
||||||
|
null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openInBrowserAfterFailing(customTabsIntent: CustomTabsIntent) {
|
private fun openInBrowserAfterFailing(customTabsIntent: CustomTabsIntent) {
|
||||||
rootView.progressBar.visibility = View.GONE
|
rootView.progressBar.visibility = View.GONE
|
||||||
activity!!.openItemUrl(
|
activity!!.openItemUrl(
|
||||||
allItems,
|
allItems,
|
||||||
pageNumber.toInt(),
|
pageNumber.toInt(),
|
||||||
url,
|
url,
|
||||||
customTabsIntent,
|
customTabsIntent,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
activity!!
|
activity!!
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val ARG_POSITION = "position"
|
private const val ARG_POSITION = "position"
|
||||||
private val ARG_ITEMS = "items"
|
private const val ARG_ITEMS = "items"
|
||||||
private val ARG_WEBVIEW = "userWebview"
|
|
||||||
|
|
||||||
fun newInstance(
|
fun newInstance(
|
||||||
position: Int,
|
position: Int,
|
||||||
allItems: ArrayList<Item>,
|
allItems: ArrayList<Item>
|
||||||
webview: Boolean
|
|
||||||
): ArticleFragment {
|
): ArticleFragment {
|
||||||
val fragment = ArticleFragment()
|
val fragment = ArticleFragment()
|
||||||
val args = Bundle()
|
val args = Bundle()
|
||||||
args.putInt(ARG_POSITION, position)
|
args.putInt(ARG_POSITION, position)
|
||||||
args.putBoolean(ARG_WEBVIEW, webview)
|
|
||||||
args.putParcelableArrayList(ARG_ITEMS, allItems)
|
args.putParcelableArrayList(ARG_ITEMS, allItems)
|
||||||
fragment.arguments = args
|
fragment.arguments = args
|
||||||
return fragment
|
return fragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
package apps.amine.bou.readerforselfoss.persistence.dao
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Delete
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface ActionsDao {
|
||||||
|
@Query("SELECT * FROM actions order by id asc")
|
||||||
|
fun actions(): List<ActionEntity>
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
fun insertAllActions(vararg actions: ActionEntity)
|
||||||
|
|
||||||
|
@Query("DELETE FROM actions WHERE articleid = :article_id AND read = 1")
|
||||||
|
fun deleteReadActionForArticle(article_id: String)
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
fun delete(action: ActionEntity)
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
package apps.amine.bou.readerforselfoss.persistence.dao
|
||||||
|
|
||||||
|
import androidx.room.Delete
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import apps.amine.bou.readerforselfoss.persistence.entities.SourceEntity
|
||||||
|
import apps.amine.bou.readerforselfoss.persistence.entities.TagEntity
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface DrawerDataDao {
|
||||||
|
@Query("SELECT * FROM tags")
|
||||||
|
fun tags(): List<TagEntity>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM sources")
|
||||||
|
fun sources(): List<SourceEntity>
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
fun insertAllTags(vararg tags: TagEntity)
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
fun insertAllSources(vararg sources: SourceEntity)
|
||||||
|
|
||||||
|
@Query("DELETE FROM tags")
|
||||||
|
fun deleteAllTags()
|
||||||
|
|
||||||
|
@Query("DELETE FROM sources")
|
||||||
|
fun deleteAllSources()
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
fun deleteTag(tag: TagEntity)
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
fun deleteSource(source: SourceEntity)
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package apps.amine.bou.readerforselfoss.persistence.dao
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Delete
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import apps.amine.bou.readerforselfoss.persistence.entities.ItemEntity
|
||||||
|
import androidx.room.Update
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface ItemsDao {
|
||||||
|
@Query("SELECT * FROM items order by id desc")
|
||||||
|
fun items(): List<ItemEntity>
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
fun insertAllItems(vararg items: ItemEntity)
|
||||||
|
|
||||||
|
@Query("DELETE FROM items")
|
||||||
|
fun deleteAllItems()
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
fun delete(item: ItemEntity)
|
||||||
|
|
||||||
|
@Update
|
||||||
|
fun updateItem(item: ItemEntity)
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package apps.amine.bou.readerforselfoss.persistence.database
|
||||||
|
|
||||||
|
import androidx.room.RoomDatabase
|
||||||
|
import androidx.room.Database
|
||||||
|
import apps.amine.bou.readerforselfoss.persistence.dao.ActionsDao
|
||||||
|
import apps.amine.bou.readerforselfoss.persistence.dao.DrawerDataDao
|
||||||
|
import apps.amine.bou.readerforselfoss.persistence.dao.ItemsDao
|
||||||
|
import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity
|
||||||
|
import apps.amine.bou.readerforselfoss.persistence.entities.ItemEntity
|
||||||
|
import apps.amine.bou.readerforselfoss.persistence.entities.SourceEntity
|
||||||
|
import apps.amine.bou.readerforselfoss.persistence.entities.TagEntity
|
||||||
|
|
||||||
|
@Database(entities = [TagEntity::class, SourceEntity::class, ItemEntity::class, ActionEntity::class], version = 3)
|
||||||
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
|
abstract fun drawerDataDao(): DrawerDataDao
|
||||||
|
|
||||||
|
abstract fun itemsDao(): ItemsDao
|
||||||
|
|
||||||
|
abstract fun actionsDao(): ActionsDao
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package apps.amine.bou.readerforselfoss.persistence.entities
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
|
@Entity(tableName = "actions")
|
||||||
|
data class ActionEntity(
|
||||||
|
@ColumnInfo(name = "articleid")
|
||||||
|
val articleId: String,
|
||||||
|
@ColumnInfo(name = "read")
|
||||||
|
val read: Boolean,
|
||||||
|
@ColumnInfo(name = "unread")
|
||||||
|
val unread: Boolean,
|
||||||
|
@ColumnInfo(name = "starred")
|
||||||
|
var starred: Boolean,
|
||||||
|
@ColumnInfo(name = "unstarred")
|
||||||
|
var unstarred: Boolean
|
||||||
|
) {
|
||||||
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
var id: Int = 0
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package apps.amine.bou.readerforselfoss.persistence.entities
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
|
@Entity(tableName = "tags")
|
||||||
|
data class TagEntity(
|
||||||
|
@PrimaryKey
|
||||||
|
@ColumnInfo(name = "tag")
|
||||||
|
val tag: String,
|
||||||
|
@ColumnInfo(name = "color")
|
||||||
|
val color: String,
|
||||||
|
@ColumnInfo(name = "unread")
|
||||||
|
val unread: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
@Entity(tableName = "sources")
|
||||||
|
data class SourceEntity(
|
||||||
|
@PrimaryKey
|
||||||
|
@ColumnInfo(name = "id")
|
||||||
|
val id: String,
|
||||||
|
@ColumnInfo(name = "title")
|
||||||
|
val title: String,
|
||||||
|
@ColumnInfo(name = "tags")
|
||||||
|
val tags: String,
|
||||||
|
@ColumnInfo(name = "spout")
|
||||||
|
val spout: String,
|
||||||
|
@ColumnInfo(name = "error")
|
||||||
|
val error: String,
|
||||||
|
@ColumnInfo(name = "icon")
|
||||||
|
val icon: String
|
||||||
|
)
|
@ -0,0 +1,32 @@
|
|||||||
|
package apps.amine.bou.readerforselfoss.persistence.entities
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
|
@Entity(tableName = "items")
|
||||||
|
data class ItemEntity(
|
||||||
|
@PrimaryKey
|
||||||
|
@ColumnInfo(name = "id")
|
||||||
|
val id: String,
|
||||||
|
@ColumnInfo(name = "datetime")
|
||||||
|
val datetime: String,
|
||||||
|
@ColumnInfo(name = "title")
|
||||||
|
val title: String,
|
||||||
|
@ColumnInfo(name = "content")
|
||||||
|
val content: String,
|
||||||
|
@ColumnInfo(name = "unread")
|
||||||
|
val unread: Boolean,
|
||||||
|
@ColumnInfo(name = "starred")
|
||||||
|
var starred: Boolean,
|
||||||
|
@ColumnInfo(name = "thumbnail")
|
||||||
|
val thumbnail: String,
|
||||||
|
@ColumnInfo(name = "icon")
|
||||||
|
val icon: String,
|
||||||
|
@ColumnInfo(name = "link")
|
||||||
|
val link: String,
|
||||||
|
@ColumnInfo(name = "sourcetitle")
|
||||||
|
val sourcetitle: String,
|
||||||
|
@ColumnInfo(name = "tags")
|
||||||
|
val tags: String
|
||||||
|
)
|
@ -0,0 +1,16 @@
|
|||||||
|
package apps.amine.bou.readerforselfoss.persistence.migrations
|
||||||
|
|
||||||
|
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||||
|
import androidx.room.migration.Migration
|
||||||
|
|
||||||
|
val MIGRATION_1_2: Migration = object : Migration(1, 2) {
|
||||||
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
|
database.execSQL("CREATE TABLE IF NOT EXISTS `items` (`id` TEXT NOT NULL, `datetime` TEXT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `thumbnail` TEXT NOT NULL, `icon` TEXT NOT NULL, `link` TEXT NOT NULL, `sourcetitle` TEXT NOT NULL, `tags` TEXT NOT NULL, PRIMARY KEY(`id`))")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val MIGRATION_2_3: Migration = object : Migration(2, 3) {
|
||||||
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
|
database.execSQL("CREATE TABLE IF NOT EXISTS `actions` (`id` INTEGER NOT NULL, `articleid` TEXT NOT NULL, `read` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `unstarred` INTEGER NOT NULL, PRIMARY KEY(`id`))")
|
||||||
|
}
|
||||||
|
}
|
@ -1,38 +1,43 @@
|
|||||||
package apps.amine.bou.readerforselfoss.settings;
|
package apps.amine.bou.readerforselfoss.settings;
|
||||||
|
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.PreferenceActivity;
|
import android.preference.PreferenceActivity;
|
||||||
import android.support.annotation.LayoutRes;
|
import androidx.annotation.LayoutRes;
|
||||||
import android.support.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import android.support.design.widget.AppBarLayout;
|
import com.google.android.material.appbar.AppBarLayout;
|
||||||
import android.support.v7.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
import android.support.v7.app.AppCompatDelegate;
|
import androidx.appcompat.app.AppCompatDelegate;
|
||||||
import android.support.v7.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
import apps.amine.bou.readerforselfoss.R;
|
|
||||||
import com.ftinc.scoop.Scoop;
|
import com.ftinc.scoop.Scoop;
|
||||||
|
|
||||||
|
import apps.amine.bou.readerforselfoss.R;
|
||||||
|
import apps.amine.bou.readerforselfoss.themes.AppColors;
|
||||||
|
import apps.amine.bou.readerforselfoss.themes.Toppings;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link PreferenceActivity} which implements and proxies the necessary calls
|
* A {@link PreferenceActivity} which implements and proxies the necessary calls
|
||||||
* to be used with AppCompat.
|
* to be used with AppCompat.
|
||||||
*/
|
*/
|
||||||
public abstract class AppCompatPreferenceActivity extends PreferenceActivity { //NOSONAR
|
public abstract class AppCompatPreferenceActivity extends PreferenceActivity {
|
||||||
|
|
||||||
private AppCompatDelegate mDelegate;
|
private AppCompatDelegate mDelegate;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
new AppColors(this);
|
||||||
|
|
||||||
getDelegate().installViewFactory();
|
getDelegate().installViewFactory();
|
||||||
getDelegate().onCreate(savedInstanceState);
|
getDelegate().onCreate(savedInstanceState);
|
||||||
Scoop.getInstance().apply(this);
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,9 +45,16 @@ public abstract class AppCompatPreferenceActivity extends PreferenceActivity { /
|
|||||||
protected void onPostCreate(Bundle savedInstanceState) {
|
protected void onPostCreate(Bundle savedInstanceState) {
|
||||||
super.onPostCreate(savedInstanceState);
|
super.onPostCreate(savedInstanceState);
|
||||||
|
|
||||||
LinearLayout root = (LinearLayout)findViewById(android.R.id.list).getParent().getParent().getParent();
|
LinearLayout root = (LinearLayout) findViewById(android.R.id.list).getParent().getParent().getParent();
|
||||||
AppBarLayout bar = (AppBarLayout) LayoutInflater.from(this).inflate(R.layout.settings_toolbar, root, false);
|
AppBarLayout bar = (AppBarLayout) LayoutInflater.from(this).inflate(R.layout.settings_toolbar, root, false);
|
||||||
Toolbar toolbar = bar.findViewById(R.id.toolbar);
|
Toolbar toolbar = bar.findViewById(R.id.toolbar);
|
||||||
|
|
||||||
|
Scoop scoop = Scoop.getInstance();
|
||||||
|
scoop.bind(this, Toppings.PRIMARY.getValue(), toolbar);
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
getSupportActionBar().setDisplayShowHomeEnabled(true);
|
getSupportActionBar().setDisplayShowHomeEnabled(true);
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
package apps.amine.bou.readerforselfoss.settings;
|
package apps.amine.bou.readerforselfoss.settings;
|
||||||
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.content.ClipData;
|
import android.content.ClipData;
|
||||||
import android.content.ClipboardManager;
|
import android.content.ClipboardManager;
|
||||||
@ -21,16 +19,21 @@ import android.preference.PreferenceActivity;
|
|||||||
import android.preference.PreferenceFragment;
|
import android.preference.PreferenceFragment;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.preference.SwitchPreference;
|
import android.preference.SwitchPreference;
|
||||||
import android.support.v7.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
|
import android.text.Editable;
|
||||||
import android.text.InputFilter;
|
import android.text.InputFilter;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
|
import android.text.TextWatcher;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import apps.amine.bou.readerforselfoss.BuildConfig;
|
import java.util.List;
|
||||||
|
|
||||||
import apps.amine.bou.readerforselfoss.R;
|
import apps.amine.bou.readerforselfoss.R;
|
||||||
|
import apps.amine.bou.readerforselfoss.themes.AppColors;
|
||||||
import apps.amine.bou.readerforselfoss.utils.Config;
|
import apps.amine.bou.readerforselfoss.utils.Config;
|
||||||
import com.ftinc.scoop.ui.ScoopSettingsActivity;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -44,7 +47,7 @@ import com.ftinc.scoop.ui.ScoopSettingsActivity;
|
|||||||
* href="http://developer.android.com/guide/topics/ui/settings.html">Settings
|
* href="http://developer.android.com/guide/topics/ui/settings.html">Settings
|
||||||
* API Guide</a> for more information on developing a Settings UI.
|
* API Guide</a> for more information on developing a Settings UI.
|
||||||
*/
|
*/
|
||||||
public class SettingsActivity extends AppCompatPreferenceActivity { //NOSONAR
|
public class SettingsActivity extends AppCompatPreferenceActivity {
|
||||||
/**
|
/**
|
||||||
* A preference value change listener that updates the preference's summary
|
* A preference value change listener that updates the preference's summary
|
||||||
* to reflect its new value.
|
* to reflect its new value.
|
||||||
@ -90,6 +93,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity { //NOSONAR
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
new AppColors(this);
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setupActionBar();
|
setupActionBar();
|
||||||
}
|
}
|
||||||
@ -130,8 +134,11 @@ public class SettingsActivity extends AppCompatPreferenceActivity { //NOSONAR
|
|||||||
protected boolean isValidFragment(String fragmentName) {
|
protected boolean isValidFragment(String fragmentName) {
|
||||||
return PreferenceFragment.class.getName().equals(fragmentName)
|
return PreferenceFragment.class.getName().equals(fragmentName)
|
||||||
|| GeneralPreferenceFragment.class.getName().equals(fragmentName)
|
|| GeneralPreferenceFragment.class.getName().equals(fragmentName)
|
||||||
|
|| ArticleViewerPreferenceFragment.class.getName().equals(fragmentName)
|
||||||
|
|| OfflinePreferenceFragment.class.getName().equals(fragmentName)
|
||||||
|| DebugPreferenceFragment.class.getName().equals(fragmentName)
|
|| DebugPreferenceFragment.class.getName().equals(fragmentName)
|
||||||
|| LinksPreferenceFragment.class.getName().equals(fragmentName);
|
|| LinksPreferenceFragment.class.getName().equals(fragmentName)
|
||||||
|
|| ThemePreferenceFragment.class.getName().equals(fragmentName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -146,34 +153,74 @@ public class SettingsActivity extends AppCompatPreferenceActivity { //NOSONAR
|
|||||||
addPreferencesFromResource(R.xml.pref_general);
|
addPreferencesFromResource(R.xml.pref_general);
|
||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
|
|
||||||
SwitchPreference cardViewActive = (SwitchPreference) findPreference("card_view_active");
|
|
||||||
final SwitchPreference tabOnTap = (SwitchPreference) findPreference("tab_on_tap");
|
|
||||||
tabOnTap.setEnabled(!cardViewActive.isChecked());
|
|
||||||
cardViewActive.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
|
|
||||||
public boolean onPreferenceChange(Preference preference, Object newValue){
|
|
||||||
boolean isEnabled = (Boolean) newValue;
|
|
||||||
tabOnTap.setEnabled(!isEnabled);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
EditTextPreference itemsNumber = (EditTextPreference) findPreference("prefer_api_items_number");
|
EditTextPreference itemsNumber = (EditTextPreference) findPreference("prefer_api_items_number");
|
||||||
itemsNumber.getEditText().setFilters(new InputFilter[]{
|
itemsNumber.getEditText().setFilters(new InputFilter[]{
|
||||||
new InputFilter (){
|
new InputFilter() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
|
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
|
||||||
try {
|
try {
|
||||||
int input = Integer.parseInt(dest.toString() + source.toString());
|
int input = Integer.parseInt(dest.toString() + source.toString());
|
||||||
if (input <= 200 && input >0)
|
if (input <= 200 && input > 0)
|
||||||
return null;
|
return null;
|
||||||
} catch (NumberFormatException nfe) {
|
} catch (NumberFormatException nfe) {
|
||||||
Toast.makeText(getActivity(), R.string.items_number_should_be_number, Toast.LENGTH_LONG).show();
|
Toast.makeText(getActivity(), R.string.items_number_should_be_number, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
int id = item.getItemId();
|
||||||
|
if (id == android.R.id.home) {
|
||||||
|
getActivity().finish();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||||
|
public static class ArticleViewerPreferenceFragment extends PreferenceFragment {
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
addPreferencesFromResource(R.xml.pref_viewer);
|
||||||
|
setHasOptionsMenu(true);
|
||||||
|
|
||||||
|
final EditTextPreference fontSize = (EditTextPreference) findPreference("reader_font_size");
|
||||||
|
fontSize.getEditText().addTextChangedListener(new TextWatcher() {
|
||||||
|
@Override
|
||||||
|
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterTextChanged(Editable editable) {
|
||||||
|
try {
|
||||||
|
fontSize.getEditText().setTextSize(Integer.parseInt(editable.toString()));
|
||||||
|
} catch (NumberFormatException e) {}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
fontSize.getEditText().setFilters(new InputFilter[]{
|
||||||
|
new InputFilter() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
|
||||||
|
try {
|
||||||
|
int input = Integer.parseInt(dest.toString() + source.toString());
|
||||||
|
if (input > 0)
|
||||||
|
return null;
|
||||||
|
} catch (NumberFormatException nfe) {}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -195,7 +242,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity { //NOSONAR
|
|||||||
addPreferencesFromResource(R.xml.pref_debug);
|
addPreferencesFromResource(R.xml.pref_debug);
|
||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
|
|
||||||
SharedPreferences pref = getActivity().getSharedPreferences(Config.Companion.getSettingsName(), Context.MODE_PRIVATE);
|
SharedPreferences pref = getActivity().getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE);
|
||||||
final String id = pref.getString("unique_id", "...");
|
final String id = pref.getString("unique_id", "...");
|
||||||
|
|
||||||
final Preference identifier = findPreference("debug_identifier");
|
final Preference identifier = findPreference("debug_identifier");
|
||||||
@ -205,11 +252,14 @@ public class SettingsActivity extends AppCompatPreferenceActivity { //NOSONAR
|
|||||||
identifier.setOnPreferenceClickListener(new OnPreferenceClickListener() {
|
identifier.setOnPreferenceClickListener(new OnPreferenceClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public boolean onPreferenceClick(Preference preference) {
|
public boolean onPreferenceClick(Preference preference) {
|
||||||
ClipData clip = ClipData.newPlainText("Selfoss unique id", id);
|
if (clipboard != null) {
|
||||||
clipboard.setPrimaryClip(clip);
|
ClipData clip = ClipData.newPlainText("Selfoss unique id", id);
|
||||||
|
clipboard.setPrimaryClip(clip);
|
||||||
|
|
||||||
Toast.makeText(getActivity(), R.string.unique_id_to_clipboard, Toast.LENGTH_LONG).show();
|
Toast.makeText(getActivity(), R.string.unique_id_to_clipboard, Toast.LENGTH_LONG).show();
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
identifier.setTitle(id);
|
identifier.setTitle(id);
|
||||||
@ -243,10 +293,10 @@ public class SettingsActivity extends AppCompatPreferenceActivity { //NOSONAR
|
|||||||
addPreferencesFromResource(R.xml.pref_links);
|
addPreferencesFromResource(R.xml.pref_links);
|
||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
|
|
||||||
findPreference( "trackerLink" ).setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
findPreference("trackerLink").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public boolean onPreferenceClick(Preference preference) {
|
public boolean onPreferenceClick(Preference preference) {
|
||||||
openUrl(Uri.parse(BuildConfig.TRACKER_URL));
|
openUrl(Uri.parse(Config.trackerUrl));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -254,7 +304,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity { //NOSONAR
|
|||||||
findPreference("sourceLink").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
findPreference("sourceLink").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public boolean onPreferenceClick(Preference preference) {
|
public boolean onPreferenceClick(Preference preference) {
|
||||||
openUrl(Uri.parse(BuildConfig.SOURCE_URL));
|
openUrl(Uri.parse(Config.sourceUrl));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -262,7 +312,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity { //NOSONAR
|
|||||||
findPreference("translation").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
findPreference("translation").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public boolean onPreferenceClick(Preference preference) {
|
public boolean onPreferenceClick(Preference preference) {
|
||||||
openUrl(Uri.parse(BuildConfig.TRANSLATION_URL));
|
openUrl(Uri.parse(Config.translationUrl));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -279,17 +329,62 @@ public class SettingsActivity extends AppCompatPreferenceActivity { //NOSONAR
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||||
public void onHeaderClick(Header header, int position) {
|
public static class ThemePreferenceFragment extends PreferenceFragment {
|
||||||
super.onHeaderClick(header, position);
|
@Override
|
||||||
if (header.id == R.id.theme_change) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
Intent intent = ScoopSettingsActivity.createIntent(getApplicationContext());
|
super.onCreate(savedInstanceState);
|
||||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
addPreferencesFromResource(R.xml.pref_theme);
|
||||||
getApplicationContext().startActivity(intent);
|
setHasOptionsMenu(true);
|
||||||
finish();
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
int id = item.getItemId();
|
||||||
|
if (id == android.R.id.home) {
|
||||||
|
getActivity().finish();
|
||||||
|
return true;
|
||||||
|
} else if (id == R.id.clear) {
|
||||||
|
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||||
|
SharedPreferences.Editor editor = pref.edit();
|
||||||
|
editor.remove("color_primary");
|
||||||
|
editor.remove("color_primary_dark");
|
||||||
|
editor.remove("color_accent");
|
||||||
|
editor.remove("color_accent_dark");
|
||||||
|
editor.remove("dark_theme");
|
||||||
|
editor.apply();
|
||||||
|
getActivity().finish();
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
|
inflater.inflate(R.menu.settings_theme, menu);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||||
|
public static class OfflinePreferenceFragment extends PreferenceFragment {
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
addPreferencesFromResource(R.xml.pref_offline);
|
||||||
|
setHasOptionsMenu(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
int id = item.getItemId();
|
||||||
|
if (id == android.R.id.home) {
|
||||||
|
getActivity().finish();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
int id = item.getItemId();
|
int id = item.getItemId();
|
||||||
|
@ -2,49 +2,68 @@ package apps.amine.bou.readerforselfoss.themes
|
|||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.support.annotation.ColorInt
|
import android.preference.PreferenceManager
|
||||||
|
import androidx.annotation.ColorInt
|
||||||
|
import androidx.appcompat.view.ContextThemeWrapper
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import apps.amine.bou.readerforselfoss.R
|
import apps.amine.bou.readerforselfoss.R
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
|
||||||
class AppColors(a: Activity) {
|
class AppColors(a: Activity) {
|
||||||
@ColorInt val accent: Int
|
|
||||||
@ColorInt val dark: Int
|
@ColorInt val colorPrimary: Int
|
||||||
@ColorInt val primary: Int
|
@ColorInt val colorPrimaryDark: Int
|
||||||
@ColorInt val cardBackground: Int
|
@ColorInt val colorAccent: Int
|
||||||
@ColorInt val windowBackground: Int
|
@ColorInt val colorAccentDark: Int
|
||||||
|
@ColorInt val cardBackgroundColor: Int
|
||||||
|
@ColorInt val colorBackground: Int
|
||||||
val isDarkTheme: Boolean
|
val isDarkTheme: Boolean
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
val sharedPref = PreferenceManager.getDefaultSharedPreferences(a)
|
||||||
|
|
||||||
|
colorPrimary =
|
||||||
|
sharedPref.getInt(
|
||||||
|
"color_primary",
|
||||||
|
a.resources.getColor(R.color.colorPrimary)
|
||||||
|
)
|
||||||
|
colorPrimaryDark =
|
||||||
|
sharedPref.getInt(
|
||||||
|
"color_primary_dark",
|
||||||
|
a.resources.getColor(R.color.colorPrimaryDark)
|
||||||
|
)
|
||||||
|
colorAccent =
|
||||||
|
sharedPref.getInt(
|
||||||
|
"color_accent",
|
||||||
|
a.resources.getColor(R.color.colorAccent)
|
||||||
|
)
|
||||||
|
colorAccentDark =
|
||||||
|
sharedPref.getInt(
|
||||||
|
"color_accent_dark",
|
||||||
|
a.resources.getColor(R.color.colorAccentDark)
|
||||||
|
)
|
||||||
|
isDarkTheme =
|
||||||
|
sharedPref.getBoolean(
|
||||||
|
"dark_theme",
|
||||||
|
false
|
||||||
|
)
|
||||||
|
|
||||||
|
colorBackground = if (isDarkTheme) {
|
||||||
|
a.setTheme(R.style.NoBarDark)
|
||||||
|
R.color.darkBackground
|
||||||
|
} else {
|
||||||
|
a.setTheme(R.style.NoBar)
|
||||||
|
android.R.color.background_light
|
||||||
|
}
|
||||||
|
|
||||||
val wrapper = Context::class.java
|
val wrapper = Context::class.java
|
||||||
val method = wrapper!!.getMethod("getThemeResId")
|
val method = wrapper!!.getMethod("getThemeResId")
|
||||||
method.isAccessible = true
|
method.isAccessible = true
|
||||||
|
|
||||||
isDarkTheme = when (method.invoke(a.baseContext)) {
|
|
||||||
R.style.NoBarTealOrangeDark,
|
|
||||||
R.style.NoBarDark,
|
|
||||||
R.style.NoBarBlueAmberDark,
|
|
||||||
R.style.NoBarGreyOrangeDark,
|
|
||||||
R.style.NoBarIndigoPinkDark,
|
|
||||||
R.style.NoBarRedTealDark,
|
|
||||||
R.style.NoBarCyanPinkDark -> true
|
|
||||||
else -> false
|
|
||||||
}
|
|
||||||
|
|
||||||
val typedAccent = TypedValue()
|
|
||||||
val typedAccentDark = TypedValue()
|
|
||||||
val typedPrimary = TypedValue()
|
|
||||||
val typedCardBackground = TypedValue()
|
val typedCardBackground = TypedValue()
|
||||||
val typedWindowBackground = TypedValue()
|
|
||||||
|
|
||||||
a.theme.resolveAttribute(R.attr.colorAccent, typedAccent, true)
|
|
||||||
a.theme.resolveAttribute(R.attr.colorAccent, typedAccent, true)
|
|
||||||
a.theme.resolveAttribute(R.attr.colorPrimary, typedPrimary, true)
|
|
||||||
a.theme.resolveAttribute(R.attr.cardBackgroundColor, typedCardBackground, true)
|
a.theme.resolveAttribute(R.attr.cardBackgroundColor, typedCardBackground, true)
|
||||||
a.theme.resolveAttribute(android.R.attr.colorBackground, typedWindowBackground, true)
|
|
||||||
accent = typedAccent.data
|
cardBackgroundColor = typedCardBackground.data
|
||||||
dark = typedAccentDark.data
|
|
||||||
primary = typedPrimary.data
|
|
||||||
cardBackground = typedCardBackground.data
|
|
||||||
windowBackground = typedWindowBackground.data
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
package apps.amine.bou.readerforselfoss.themes
|
||||||
|
|
||||||
|
enum class Toppings(val value: Int) {
|
||||||
|
PRIMARY(1),
|
||||||
|
PRIMARY_DARK(2),
|
||||||
|
ACCENT(3),
|
||||||
|
ACCENT_DARK(4)
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
package apps.amine.bou.readerforselfoss.transformers
|
package apps.amine.bou.readerforselfoss.transformers
|
||||||
|
|
||||||
import android.support.v4.view.ViewPager
|
import androidx.viewpager.widget.ViewPager
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
|
||||||
class DepthPageTransformer : ViewPager.PageTransformer {
|
class DepthPageTransformer : ViewPager.PageTransformer {
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
package apps.amine.bou.readerforselfoss.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.preference.PreferenceManager
|
||||||
|
import org.acra.ErrorReporter
|
||||||
|
|
||||||
|
fun ErrorReporter.maybeHandleSilentException(throwable: Throwable, ctx: Context) {
|
||||||
|
val sharedPref = PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||||
|
if (sharedPref.getBoolean("acra_should_log", false)) {
|
||||||
|
this.handleSilentException(throwable)
|
||||||
|
}
|
||||||
|
}
|
@ -4,4 +4,4 @@ import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
|
|||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
|
|
||||||
fun Response<SuccessResponse>.succeeded(): Boolean =
|
fun Response<SuccessResponse>.succeeded(): Boolean =
|
||||||
this.code() === 200 && this.body() != null && this.body()!!.isSuccess
|
this.code() === 200 && this.body() != null && this.body()!!.isSuccess
|
@ -2,59 +2,10 @@ package apps.amine.bou.readerforselfoss.utils
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.net.Uri
|
|
||||||
import android.support.v7.app.AlertDialog
|
|
||||||
import apps.amine.bou.readerforselfoss.R
|
import apps.amine.bou.readerforselfoss.R
|
||||||
import com.google.firebase.remoteconfig.FirebaseRemoteConfig
|
|
||||||
|
|
||||||
fun String?.isEmptyOrNullOrNullString(): Boolean =
|
fun String?.isEmptyOrNullOrNullString(): Boolean =
|
||||||
this == null || this == "null" || this.isEmpty()
|
this == null || this == "null" || this.isEmpty()
|
||||||
|
|
||||||
fun Context.checkApkVersion(
|
|
||||||
settings: SharedPreferences,
|
|
||||||
editor: SharedPreferences.Editor,
|
|
||||||
mFirebaseRemoteConfig: FirebaseRemoteConfig
|
|
||||||
) = {
|
|
||||||
fun isThereAnUpdate() {
|
|
||||||
val APK_LINK = "github_apk"
|
|
||||||
|
|
||||||
val apkLink = mFirebaseRemoteConfig.getString(APK_LINK)
|
|
||||||
val storedLink = settings.getString(APK_LINK, "")
|
|
||||||
if (apkLink != storedLink && !apkLink.isEmpty()) {
|
|
||||||
val alertDialog = AlertDialog.Builder(this).create()
|
|
||||||
alertDialog.setTitle(getString(R.string.new_apk_available_title))
|
|
||||||
alertDialog.setMessage(getString(R.string.new_apk_available_message))
|
|
||||||
alertDialog.setButton(
|
|
||||||
AlertDialog.BUTTON_POSITIVE,
|
|
||||||
getString(R.string.new_apk_available_get)
|
|
||||||
) { _, _ ->
|
|
||||||
editor.putString(APK_LINK, apkLink)
|
|
||||||
editor.apply()
|
|
||||||
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(apkLink))
|
|
||||||
startActivity(browserIntent)
|
|
||||||
}
|
|
||||||
alertDialog.setButton(
|
|
||||||
AlertDialog.BUTTON_NEUTRAL, getString(R.string.new_apk_available_no),
|
|
||||||
{ dialog, _ ->
|
|
||||||
editor.putString(APK_LINK, apkLink)
|
|
||||||
editor.apply()
|
|
||||||
dialog.dismiss()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
alertDialog.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mFirebaseRemoteConfig.fetch(43200)
|
|
||||||
.addOnCompleteListener { task ->
|
|
||||||
if (task.isSuccessful) {
|
|
||||||
mFirebaseRemoteConfig.activateFetched()
|
|
||||||
}
|
|
||||||
|
|
||||||
isThereAnUpdate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun String.longHash(): Long {
|
fun String.longHash(): Long {
|
||||||
var h = 98764321261L
|
var h = 98764321261L
|
||||||
@ -68,11 +19,11 @@ fun String.longHash(): Long {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun String.toStringUriWithHttp(): String =
|
fun String.toStringUriWithHttp(): String =
|
||||||
if (!this.startsWith("https://") && !this.startsWith("http://")) {
|
if (!this.startsWith("https://") && !this.startsWith("http://")) {
|
||||||
"http://" + this
|
"http://" + this
|
||||||
} else {
|
} else {
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Context.shareLink(itemUrl: String) {
|
fun Context.shareLink(itemUrl: String) {
|
||||||
val sendIntent = Intent()
|
val sendIntent = Intent()
|
||||||
@ -81,9 +32,9 @@ fun Context.shareLink(itemUrl: String) {
|
|||||||
sendIntent.putExtra(Intent.EXTRA_TEXT, itemUrl.toStringUriWithHttp())
|
sendIntent.putExtra(Intent.EXTRA_TEXT, itemUrl.toStringUriWithHttp())
|
||||||
sendIntent.type = "text/plain"
|
sendIntent.type = "text/plain"
|
||||||
startActivity(
|
startActivity(
|
||||||
Intent.createChooser(
|
Intent.createChooser(
|
||||||
sendIntent,
|
sendIntent,
|
||||||
getString(R.string.share)
|
getString(R.string.share)
|
||||||
).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -26,13 +26,25 @@ class Config(c: Context) {
|
|||||||
get() = settings.getString("httpPassword", "")
|
get() = settings.getString("httpPassword", "")
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val settingsName = "paramsselfoss"
|
const val settingsName = "paramsselfoss"
|
||||||
|
|
||||||
|
const val feedbackEmail = "aminecmi@gmail.com"
|
||||||
|
|
||||||
|
const val translationUrl = "https://crwd.in/readerforselfoss"
|
||||||
|
|
||||||
|
const val sourceUrl = "https://github.com/aminecmi/ReaderforSelfoss"
|
||||||
|
|
||||||
|
const val trackerUrl = "https://github.com/aminecmi/ReaderforSelfoss/issues"
|
||||||
|
|
||||||
|
const val syncChannelId = "sync-channel-id"
|
||||||
|
|
||||||
|
const val newItemsChannelId = "new-items-channel-id"
|
||||||
|
|
||||||
fun logoutAndRedirect(
|
fun logoutAndRedirect(
|
||||||
c: Context,
|
c: Context,
|
||||||
callingActivity: Activity,
|
callingActivity: Activity,
|
||||||
editor: SharedPreferences.Editor,
|
editor: SharedPreferences.Editor,
|
||||||
baseUrlFail: Boolean = false
|
baseUrlFail: Boolean = false
|
||||||
): Boolean {
|
): Boolean {
|
||||||
editor.remove("url")
|
editor.remove("url")
|
||||||
editor.remove("login")
|
editor.remove("login")
|
||||||
|
@ -7,37 +7,37 @@ import javax.net.ssl.SSLContext
|
|||||||
import javax.net.ssl.TrustManager
|
import javax.net.ssl.TrustManager
|
||||||
import javax.net.ssl.X509TrustManager
|
import javax.net.ssl.X509TrustManager
|
||||||
|
|
||||||
fun getUnsafeHttpClient() =
|
fun getUnsafeHttpClient(): OkHttpClient.Builder =
|
||||||
try {
|
try {
|
||||||
// Create a trust manager that does not validate certificate chains
|
// Create a trust manager that does not validate certificate chains
|
||||||
val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager {
|
val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager {
|
||||||
override fun getAcceptedIssuers(): Array<X509Certificate> =
|
override fun getAcceptedIssuers(): Array<X509Certificate> =
|
||||||
arrayOf()
|
arrayOf()
|
||||||
|
|
||||||
@Throws(CertificateException::class)
|
@Throws(CertificateException::class)
|
||||||
override fun checkClientTrusted(
|
override fun checkClientTrusted(
|
||||||
chain: Array<java.security.cert.X509Certificate>,
|
chain: Array<java.security.cert.X509Certificate>,
|
||||||
authType: String
|
authType: String
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(CertificateException::class)
|
@Throws(CertificateException::class)
|
||||||
override fun checkServerTrusted(
|
override fun checkServerTrusted(
|
||||||
chain: Array<java.security.cert.X509Certificate>,
|
chain: Array<java.security.cert.X509Certificate>,
|
||||||
authType: String
|
authType: String
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Install the all-trusting trust manager
|
// Install the all-trusting trust manager
|
||||||
val sslContext = SSLContext.getInstance("SSL")
|
val sslContext = SSLContext.getInstance("SSL")
|
||||||
sslContext.init(null, trustAllCerts, java.security.SecureRandom())
|
sslContext.init(null, trustAllCerts, java.security.SecureRandom())
|
||||||
|
|
||||||
val sslSocketFactory = sslContext.socketFactory
|
val sslSocketFactory = sslContext.socketFactory
|
||||||
|
|
||||||
OkHttpClient.Builder()
|
OkHttpClient.Builder()
|
||||||
.sslSocketFactory(sslSocketFactory, trustAllCerts[0] as X509TrustManager)
|
.sslSocketFactory(sslSocketFactory, trustAllCerts[0] as X509TrustManager)
|
||||||
.hostnameVerifier { _, _ -> true }
|
.hostnameVerifier { _, _ -> true }
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
throw RuntimeException(e)
|
throw RuntimeException(e)
|
||||||
}
|
}
|
@ -1,15 +1,22 @@
|
|||||||
package apps.amine.bou.readerforselfoss.utils
|
package apps.amine.bou.readerforselfoss.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.text.format.DateUtils
|
import android.text.format.DateUtils
|
||||||
import apps.amine.bou.readerforselfoss.api.selfoss.Item
|
import apps.amine.bou.readerforselfoss.api.selfoss.Item
|
||||||
|
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossTagType
|
||||||
|
import org.acra.ACRA
|
||||||
import java.text.ParseException
|
import java.text.ParseException
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
fun String.toTextDrawableString(): String {
|
fun String.toTextDrawableString(c: Context): String {
|
||||||
val textDrawable = StringBuilder()
|
val textDrawable = StringBuilder()
|
||||||
for (s in this.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) {
|
for (s in this.split(" ".toRegex()).filter { !it.isEmpty() }.toTypedArray()) {
|
||||||
textDrawable.append(s[0])
|
try {
|
||||||
|
textDrawable.append(s[0])
|
||||||
|
} catch (e: StringIndexOutOfBoundsException) {
|
||||||
|
ACRA.getErrorReporter().maybeHandleSilentException(e, c)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return textDrawable.toString()
|
return textDrawable.toString()
|
||||||
}
|
}
|
||||||
@ -17,10 +24,10 @@ fun String.toTextDrawableString(): String {
|
|||||||
fun Item.sourceAndDateText(): String {
|
fun Item.sourceAndDateText(): String {
|
||||||
val formattedDate: String = try {
|
val formattedDate: String = try {
|
||||||
" " + DateUtils.getRelativeTimeSpanString(
|
" " + DateUtils.getRelativeTimeSpanString(
|
||||||
SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(this.datetime).time,
|
SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(this.datetime).time,
|
||||||
Date().time,
|
Date().time,
|
||||||
DateUtils.MINUTE_IN_MILLIS,
|
DateUtils.MINUTE_IN_MILLIS,
|
||||||
DateUtils.FORMAT_ABBREV_RELATIVE
|
DateUtils.FORMAT_ABBREV_RELATIVE
|
||||||
)
|
)
|
||||||
} catch (e: ParseException) {
|
} catch (e: ParseException) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
@ -28,4 +35,18 @@ fun Item.sourceAndDateText(): String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return this.sourcetitle + formattedDate
|
return this.sourcetitle + formattedDate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Item.toggleStar(): Item {
|
||||||
|
this.starred = !this.starred
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun List<Item>.flattenTags(): List<Item> =
|
||||||
|
this.flatMap {
|
||||||
|
val item = it
|
||||||
|
val tags: List<String> = it.tags.tags.split(",")
|
||||||
|
tags.map { t ->
|
||||||
|
item.copy(tags = SelfossTagType(t.trim()))
|
||||||
|
}
|
||||||
|
}
|
@ -6,8 +6,13 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.support.customtabs.CustomTabsIntent
|
import android.text.Spannable
|
||||||
|
import android.text.style.ClickableSpan
|
||||||
|
import androidx.browser.customtabs.CustomTabsIntent
|
||||||
import android.util.Patterns
|
import android.util.Patterns
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import apps.amine.bou.readerforselfoss.R
|
import apps.amine.bou.readerforselfoss.R
|
||||||
import apps.amine.bou.readerforselfoss.ReaderActivity
|
import apps.amine.bou.readerforselfoss.ReaderActivity
|
||||||
@ -20,10 +25,10 @@ fun Context.buildCustomTabsIntent(): CustomTabsIntent {
|
|||||||
val actionIntent = Intent(Intent.ACTION_SEND)
|
val actionIntent = Intent(Intent.ACTION_SEND)
|
||||||
actionIntent.type = "text/plain"
|
actionIntent.type = "text/plain"
|
||||||
val createPendingShareIntent: PendingIntent = PendingIntent.getActivity(
|
val createPendingShareIntent: PendingIntent = PendingIntent.getActivity(
|
||||||
this,
|
this,
|
||||||
0,
|
0,
|
||||||
actionIntent,
|
actionIntent,
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
|
|
||||||
val intentBuilder = CustomTabsIntent.Builder()
|
val intentBuilder = CustomTabsIntent.Builder()
|
||||||
@ -35,14 +40,14 @@ fun Context.buildCustomTabsIntent(): CustomTabsIntent {
|
|||||||
|
|
||||||
|
|
||||||
intentBuilder.setStartAnimations(
|
intentBuilder.setStartAnimations(
|
||||||
this,
|
this,
|
||||||
R.anim.slide_in_right,
|
R.anim.slide_in_right,
|
||||||
R.anim.slide_out_left
|
R.anim.slide_out_left
|
||||||
)
|
)
|
||||||
intentBuilder.setExitAnimations(
|
intentBuilder.setExitAnimations(
|
||||||
this,
|
this,
|
||||||
android.R.anim.slide_in_left,
|
android.R.anim.slide_in_left,
|
||||||
android.R.anim.slide_out_right
|
android.R.anim.slide_out_right
|
||||||
)
|
)
|
||||||
|
|
||||||
val closeicon = BitmapFactory.decodeResource(resources, R.drawable.ic_close_white_24dp)
|
val closeicon = BitmapFactory.decodeResource(resources, R.drawable.ic_close_white_24dp)
|
||||||
@ -50,8 +55,8 @@ fun Context.buildCustomTabsIntent(): CustomTabsIntent {
|
|||||||
|
|
||||||
val shareLabel = this.getString(R.string.label_share)
|
val shareLabel = this.getString(R.string.label_share)
|
||||||
val icon = BitmapFactory.decodeResource(
|
val icon = BitmapFactory.decodeResource(
|
||||||
resources,
|
resources,
|
||||||
R.drawable.ic_share_white_24dp
|
R.drawable.ic_share_white_24dp
|
||||||
)
|
)
|
||||||
intentBuilder.setActionButton(icon, shareLabel, createPendingShareIntent)
|
intentBuilder.setActionButton(icon, shareLabel, createPendingShareIntent)
|
||||||
|
|
||||||
@ -59,12 +64,12 @@ fun Context.buildCustomTabsIntent(): CustomTabsIntent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun Context.openItemUrlInternally(
|
fun Context.openItemUrlInternally(
|
||||||
allItems: ArrayList<Item>,
|
allItems: ArrayList<Item>,
|
||||||
currentItem: Int,
|
currentItem: Int,
|
||||||
linkDecoded: String,
|
linkDecoded: String,
|
||||||
customTabsIntent: CustomTabsIntent,
|
customTabsIntent: CustomTabsIntent,
|
||||||
articleViewer: Boolean,
|
articleViewer: Boolean,
|
||||||
app: Activity
|
app: Activity
|
||||||
) {
|
) {
|
||||||
if (articleViewer) {
|
if (articleViewer) {
|
||||||
ReaderActivity.allItems = allItems
|
ReaderActivity.allItems = allItems
|
||||||
@ -74,9 +79,9 @@ fun Context.openItemUrlInternally(
|
|||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
CustomTabActivityHelper.openCustomTab(
|
CustomTabActivityHelper.openCustomTab(
|
||||||
app,
|
app,
|
||||||
customTabsIntent,
|
customTabsIntent,
|
||||||
Uri.parse(linkDecoded)
|
Uri.parse(linkDecoded)
|
||||||
) { _, uri ->
|
) { _, uri ->
|
||||||
val intent = Intent(Intent.ACTION_VIEW, uri)
|
val intent = Intent(Intent.ACTION_VIEW, uri)
|
||||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
@ -89,32 +94,32 @@ fun Context.openItemUrlInternally(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun Context.openItemUrl(
|
fun Context.openItemUrl(
|
||||||
allItems: ArrayList<Item>,
|
allItems: ArrayList<Item>,
|
||||||
currentItem: Int,
|
currentItem: Int,
|
||||||
linkDecoded: String,
|
linkDecoded: String,
|
||||||
customTabsIntent: CustomTabsIntent,
|
customTabsIntent: CustomTabsIntent,
|
||||||
internalBrowser: Boolean,
|
internalBrowser: Boolean,
|
||||||
articleViewer: Boolean,
|
articleViewer: Boolean,
|
||||||
app: Activity
|
app: Activity
|
||||||
) {
|
) {
|
||||||
|
|
||||||
if (!linkDecoded.isUrlValid()) {
|
if (!linkDecoded.isUrlValid()) {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
this,
|
this,
|
||||||
this.getString(R.string.cant_open_invalid_url),
|
this.getString(R.string.cant_open_invalid_url),
|
||||||
Toast.LENGTH_LONG
|
Toast.LENGTH_LONG
|
||||||
).show()
|
).show()
|
||||||
} else {
|
} else {
|
||||||
if (!internalBrowser) {
|
if (!internalBrowser) {
|
||||||
openInBrowser(linkDecoded, app)
|
openInBrowser(linkDecoded, app)
|
||||||
} else {
|
} else {
|
||||||
this.openItemUrlInternally(
|
this.openItemUrlInternally(
|
||||||
allItems,
|
allItems,
|
||||||
currentItem,
|
currentItem,
|
||||||
linkDecoded,
|
linkDecoded,
|
||||||
customTabsIntent,
|
customTabsIntent,
|
||||||
articleViewer,
|
articleViewer,
|
||||||
app
|
app
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -127,7 +132,7 @@ private fun openInBrowser(linkDecoded: String, app: Activity) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun String.isUrlValid(): Boolean =
|
fun String.isUrlValid(): Boolean =
|
||||||
HttpUrl.parse(this) != null && Patterns.WEB_URL.matcher(this).matches()
|
HttpUrl.parse(this) != null && Patterns.WEB_URL.matcher(this).matches()
|
||||||
|
|
||||||
fun String.isBaseUrlValid(): Boolean {
|
fun String.isBaseUrlValid(): Boolean {
|
||||||
val baseUrl = HttpUrl.parse(this)
|
val baseUrl = HttpUrl.parse(this)
|
||||||
@ -146,3 +151,40 @@ fun Context.openInBrowserAsNewTask(i: Item) {
|
|||||||
intent.data = Uri.parse(i.getLinkDecoded().toStringUriWithHttp())
|
intent.data = Uri.parse(i.getLinkDecoded().toStringUriWithHttp())
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class LinkOnTouchListener: View.OnTouchListener {
|
||||||
|
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
|
||||||
|
var ret = false
|
||||||
|
val widget: TextView = v as TextView
|
||||||
|
val text: CharSequence = widget.text
|
||||||
|
val stext = Spannable.Factory.getInstance().newSpannable(text)
|
||||||
|
|
||||||
|
val action = event!!.action
|
||||||
|
|
||||||
|
if (action == MotionEvent.ACTION_UP ||
|
||||||
|
action == MotionEvent.ACTION_DOWN) {
|
||||||
|
var x: Float = event.x
|
||||||
|
var y: Float = event.y
|
||||||
|
|
||||||
|
x -= widget.totalPaddingLeft
|
||||||
|
y -= widget.totalPaddingTop
|
||||||
|
|
||||||
|
x += widget.scrollX
|
||||||
|
y += widget.scrollY
|
||||||
|
|
||||||
|
val layout = widget.layout
|
||||||
|
val line = layout.getLineForVertical(y.toInt())
|
||||||
|
val off = layout.getOffsetForHorizontal(line, x)
|
||||||
|
|
||||||
|
val link = stext.getSpans(off, off, ClickableSpan::class.java)
|
||||||
|
|
||||||
|
if (link.isNotEmpty()) {
|
||||||
|
if (action == MotionEvent.ACTION_UP) {
|
||||||
|
link[0].onClick(widget)
|
||||||
|
}
|
||||||
|
ret = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,53 +0,0 @@
|
|||||||
package apps.amine.bou.readerforselfoss.utils
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.support.design.widget.CoordinatorLayout
|
|
||||||
import android.support.design.widget.FloatingActionButton
|
|
||||||
import android.util.AttributeSet
|
|
||||||
import android.view.View
|
|
||||||
|
|
||||||
class ScrollAwareFABBehavior(
|
|
||||||
context: Context,
|
|
||||||
attrs: AttributeSet
|
|
||||||
) : CoordinatorLayout.Behavior<FloatingActionButton>() {
|
|
||||||
|
|
||||||
override fun onStartNestedScroll(
|
|
||||||
coordinatorLayout: CoordinatorLayout,
|
|
||||||
child: FloatingActionButton,
|
|
||||||
directTargetChild: View,
|
|
||||||
target: View,
|
|
||||||
nestedScrollAxes: Int
|
|
||||||
): Boolean {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onNestedScroll(
|
|
||||||
coordinatorLayout: CoordinatorLayout,
|
|
||||||
child: FloatingActionButton,
|
|
||||||
target: View,
|
|
||||||
dxConsumed: Int,
|
|
||||||
dyConsumed: Int,
|
|
||||||
dxUnconsumed: Int,
|
|
||||||
dyUnconsumed: Int
|
|
||||||
) {
|
|
||||||
super.onNestedScroll(
|
|
||||||
coordinatorLayout,
|
|
||||||
child,
|
|
||||||
target,
|
|
||||||
dxConsumed,
|
|
||||||
dyConsumed,
|
|
||||||
dxUnconsumed,
|
|
||||||
dyUnconsumed
|
|
||||||
)
|
|
||||||
if (dyConsumed > 0 && child.visibility == View.VISIBLE) {
|
|
||||||
child.hide(object : FloatingActionButton.OnVisibilityChangedListener() {
|
|
||||||
override fun onHidden(fab: FloatingActionButton?) {
|
|
||||||
super.onHidden(fab)
|
|
||||||
fab!!.visibility = View.INVISIBLE
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else if (dyConsumed < 0 && child.visibility != View.VISIBLE) {
|
|
||||||
child.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,9 @@
|
|||||||
|
package apps.amine.bou.readerforselfoss.utils
|
||||||
|
|
||||||
|
import android.content.res.Resources
|
||||||
|
|
||||||
|
val Int.toPx: Int
|
||||||
|
get() = (this * Resources.getSystem().displayMetrics.density).toInt()
|
||||||
|
|
||||||
|
val Int.toDp: Int
|
||||||
|
get() = (this / Resources.getSystem().displayMetrics.density).toInt()
|
@ -9,4 +9,4 @@ fun TextBadgeItem.removeBadge(): TextBadgeItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun TextBadgeItem.maybeShow(): TextBadgeItem =
|
fun TextBadgeItem.maybeShow(): TextBadgeItem =
|
||||||
if (this.isHidden) this.show() else this
|
if (this.isHidden) this.show() else this
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
package apps.amine.bou.readerforselfoss.utils.customtabs;
|
package apps.amine.bou.readerforselfoss.utils.customtabs;
|
||||||
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.customtabs.CustomTabsClient;
|
import androidx.browser.customtabs.CustomTabsClient;
|
||||||
import android.support.customtabs.CustomTabsIntent;
|
import androidx.browser.customtabs.CustomTabsIntent;
|
||||||
import android.support.customtabs.CustomTabsServiceConnection;
|
import androidx.browser.customtabs.CustomTabsServiceConnection;
|
||||||
import android.support.customtabs.CustomTabsSession;
|
import androidx.browser.customtabs.CustomTabsSession;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a helper class to manage the connection to the Custom Tabs Service.
|
* This is a helper class to manage the connection to the Custom Tabs Service.
|
||||||
@ -23,15 +23,15 @@ public class CustomTabActivityHelper implements ServiceConnectionCallback {
|
|||||||
/**
|
/**
|
||||||
* Opens the URL on a Custom Tab if possible. Otherwise fallsback to opening it on a WebView.
|
* Opens the URL on a Custom Tab if possible. Otherwise fallsback to opening it on a WebView.
|
||||||
*
|
*
|
||||||
* @param activity The host activity.
|
* @param activity The host activity.
|
||||||
* @param customTabsIntent a CustomTabsIntent to be used if Custom Tabs is available.
|
* @param customTabsIntent a CustomTabsIntent to be used if Custom Tabs is available.
|
||||||
* @param uri the Uri to be opened.
|
* @param uri the Uri to be opened.
|
||||||
* @param fallback a CustomTabFallback to be used if Custom Tabs is not available.
|
* @param fallback a CustomTabFallback to be used if Custom Tabs is not available.
|
||||||
*/
|
*/
|
||||||
public static void openCustomTab(Activity activity,
|
public static void openCustomTab(Activity activity,
|
||||||
CustomTabsIntent customTabsIntent,
|
CustomTabsIntent customTabsIntent,
|
||||||
Uri uri,
|
Uri uri,
|
||||||
CustomTabFallback fallback) {
|
CustomTabFallback fallback) {
|
||||||
String packageName = CustomTabsHelper.getPackageNameToUse(activity);
|
String packageName = CustomTabsHelper.getPackageNameToUse(activity);
|
||||||
|
|
||||||
//If we cant find a package name, it means theres no browser that supports
|
//If we cant find a package name, it means theres no browser that supports
|
||||||
@ -48,6 +48,7 @@ public class CustomTabActivityHelper implements ServiceConnectionCallback {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Unbinds the Activity from the Custom Tabs Service.
|
* Unbinds the Activity from the Custom Tabs Service.
|
||||||
|
*
|
||||||
* @param activity the activity that is connected to the service.
|
* @param activity the activity that is connected to the service.
|
||||||
*/
|
*/
|
||||||
public void unbindCustomTabsService(Activity activity) {
|
public void unbindCustomTabsService(Activity activity) {
|
||||||
@ -74,6 +75,7 @@ public class CustomTabActivityHelper implements ServiceConnectionCallback {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a Callback to be called when connected or disconnected from the Custom Tabs Service.
|
* Register a Callback to be called when connected or disconnected from the Custom Tabs Service.
|
||||||
|
*
|
||||||
* @param connectionCallback
|
* @param connectionCallback
|
||||||
*/
|
*/
|
||||||
public void setConnectionCallback(ConnectionCallback connectionCallback) {
|
public void setConnectionCallback(ConnectionCallback connectionCallback) {
|
||||||
@ -82,6 +84,7 @@ public class CustomTabActivityHelper implements ServiceConnectionCallback {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Binds the Activity to the Custom Tabs Service.
|
* Binds the Activity to the Custom Tabs Service.
|
||||||
|
*
|
||||||
* @param activity the activity to be binded to the service.
|
* @param activity the activity to be binded to the service.
|
||||||
*/
|
*/
|
||||||
public void bindCustomTabsService(Activity activity) {
|
public void bindCustomTabsService(Activity activity) {
|
||||||
@ -95,16 +98,15 @@ public class CustomTabActivityHelper implements ServiceConnectionCallback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see {@link CustomTabsSession#mayLaunchUrl(Uri, Bundle, List)}.
|
|
||||||
* @return true if call to mayLaunchUrl was accepted.
|
* @return true if call to mayLaunchUrl was accepted.
|
||||||
|
* @see {@link CustomTabsSession#mayLaunchUrl(Uri, Bundle, List)}.
|
||||||
*/
|
*/
|
||||||
public boolean mayLaunchUrl(Uri uri, Bundle extras, List<Bundle> otherLikelyBundles) {
|
public boolean mayLaunchUrl(Uri uri, Bundle extras, List<Bundle> otherLikelyBundles) {
|
||||||
if (mClient == null) return false;
|
if (mClient == null) return false;
|
||||||
|
|
||||||
CustomTabsSession session = getSession();
|
CustomTabsSession session = getSession();
|
||||||
if (session == null) return false;
|
return session != null && session.mayLaunchUrl(uri, extras, otherLikelyBundles);
|
||||||
|
|
||||||
return session.mayLaunchUrl(uri, extras, otherLikelyBundles);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -142,9 +144,8 @@ public class CustomTabActivityHelper implements ServiceConnectionCallback {
|
|||||||
*/
|
*/
|
||||||
public interface CustomTabFallback {
|
public interface CustomTabFallback {
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @param activity The Activity that wants to open the Uri.
|
* @param activity The Activity that wants to open the Uri.
|
||||||
* @param uri The uri to be opened by the fallback.
|
* @param uri The uri to be opened by the fallback.
|
||||||
*/
|
*/
|
||||||
void openUri(Activity activity, Uri uri);
|
void openUri(Activity activity, Uri uri);
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
package apps.amine.bou.readerforselfoss.utils.customtabs;
|
package apps.amine.bou.readerforselfoss.utils.customtabs;
|
||||||
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.pm.ResolveInfo;
|
import android.content.pm.ResolveInfo;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.support.customtabs.CustomTabsService;
|
import androidx.browser.customtabs.CustomTabsService;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import apps.amine.bou.readerforselfoss.utils.customtabs.helpers.KeepAliveService;
|
import apps.amine.bou.readerforselfoss.utils.customtabs.helpers.KeepAliveService;
|
||||||
|
|
||||||
@SuppressWarnings("ALL")
|
@SuppressWarnings("ALL")
|
||||||
@ -28,7 +28,8 @@ class CustomTabsHelper {
|
|||||||
|
|
||||||
private static String sPackageNameToUse;
|
private static String sPackageNameToUse;
|
||||||
|
|
||||||
private CustomTabsHelper() {}
|
private CustomTabsHelper() {
|
||||||
|
}
|
||||||
|
|
||||||
public static void addKeepAliveExtra(Context context, Intent intent) {
|
public static void addKeepAliveExtra(Context context, Intent intent) {
|
||||||
Intent keepAliveIntent = new Intent().setClassName(
|
Intent keepAliveIntent = new Intent().setClassName(
|
||||||
@ -40,7 +41,7 @@ class CustomTabsHelper {
|
|||||||
* Goes through all apps that handle VIEW intents and have a warmup service. Picks
|
* Goes through all apps that handle VIEW intents and have a warmup service. Picks
|
||||||
* the one chosen by the user if there is one, otherwise makes a best effort to return a
|
* the one chosen by the user if there is one, otherwise makes a best effort to return a
|
||||||
* valid package name.
|
* valid package name.
|
||||||
*
|
* <p>
|
||||||
* This is <strong>not</strong> threadsafe.
|
* This is <strong>not</strong> threadsafe.
|
||||||
*
|
*
|
||||||
* @param context {@link Context} to use for accessing {@link PackageManager}.
|
* @param context {@link Context} to use for accessing {@link PackageManager}.
|
||||||
@ -94,6 +95,7 @@ class CustomTabsHelper {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to check whether there is a specialized handler for a given intent.
|
* Used to check whether there is a specialized handler for a given intent.
|
||||||
|
*
|
||||||
* @param intent The intent to check with.
|
* @param intent The intent to check with.
|
||||||
* @return Whether there is a specialized handler for the given intent.
|
* @return Whether there is a specialized handler for the given intent.
|
||||||
*/
|
*/
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
package apps.amine.bou.readerforselfoss.utils.customtabs;
|
package apps.amine.bou.readerforselfoss.utils.customtabs;
|
||||||
|
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
|
||||||
|
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.support.customtabs.CustomTabsClient;
|
import androidx.browser.customtabs.CustomTabsClient;
|
||||||
import android.support.customtabs.CustomTabsServiceConnection;
|
import androidx.browser.customtabs.CustomTabsServiceConnection;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation for the CustomTabsServiceConnection that avoids leaking the
|
* Implementation for the CustomTabsServiceConnection that avoids leaking the
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
package apps.amine.bou.readerforselfoss.utils.customtabs;
|
package apps.amine.bou.readerforselfoss.utils.customtabs;
|
||||||
|
|
||||||
|
|
||||||
import android.support.customtabs.CustomTabsClient;
|
import androidx.browser.customtabs.CustomTabsClient;
|
||||||
|
|
||||||
|
|
||||||
public interface ServiceConnectionCallback {
|
public interface ServiceConnectionCallback {
|
||||||
/**
|
/**
|
||||||
* Called when the service is connected.
|
* Called when the service is connected.
|
||||||
|
*
|
||||||
* @param client a CustomTabsClient
|
* @param client a CustomTabsClient
|
||||||
*/
|
*/
|
||||||
void onServiceConnected(CustomTabsClient client);
|
void onServiceConnected(CustomTabsClient client);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/* From https://github.com/mikepenz/MaterialDrawer/blob/develop/app/src/main/java/com/mikepenz/materialdrawer/app/drawerItems/CustomBaseViewHolder.java */
|
/* From https://github.com/mikepenz/MaterialDrawer/blob/develop/app/src/main/java/com/mikepenz/materialdrawer/app/drawerItems/CustomBaseViewHolder.java */
|
||||||
package apps.amine.bou.readerforselfoss.utils.drawer
|
package apps.amine.bou.readerforselfoss.utils.drawer
|
||||||
|
|
||||||
import android.support.v7.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
@ -2,10 +2,10 @@
|
|||||||
package apps.amine.bou.readerforselfoss.utils.drawer
|
package apps.amine.bou.readerforselfoss.utils.drawer
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.support.annotation.ColorInt
|
import androidx.annotation.ColorInt
|
||||||
import android.support.annotation.ColorRes
|
import androidx.annotation.ColorRes
|
||||||
import android.support.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import android.support.v7.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
|
||||||
import com.mikepenz.materialdrawer.holder.ColorHolder
|
import com.mikepenz.materialdrawer.holder.ColorHolder
|
||||||
import com.mikepenz.materialdrawer.holder.ImageHolder
|
import com.mikepenz.materialdrawer.holder.ImageHolder
|
||||||
@ -15,7 +15,8 @@ import com.mikepenz.materialdrawer.util.DrawerImageLoader
|
|||||||
import com.mikepenz.materialdrawer.util.DrawerUIUtils
|
import com.mikepenz.materialdrawer.util.DrawerUIUtils
|
||||||
import com.mikepenz.materialize.util.UIUtils
|
import com.mikepenz.materialize.util.UIUtils
|
||||||
|
|
||||||
abstract class CustomUrlBasePrimaryDrawerItem<T, VH : RecyclerView.ViewHolder> : BaseDrawerItem<T, VH>() {
|
abstract class CustomUrlBasePrimaryDrawerItem<T, VH : RecyclerView.ViewHolder> :
|
||||||
|
BaseDrawerItem<T, VH>() {
|
||||||
fun withIcon(url: String): T {
|
fun withIcon(url: String): T {
|
||||||
this.icon = ImageHolder(url)
|
this.icon = ImageHolder(url)
|
||||||
return this as T
|
return this as T
|
||||||
@ -76,8 +77,8 @@ abstract class CustomUrlBasePrimaryDrawerItem<T, VH : RecyclerView.ViewHolder> :
|
|||||||
|
|
||||||
//set the background for the item
|
//set the background for the item
|
||||||
UIUtils.setBackground(
|
UIUtils.setBackground(
|
||||||
viewHolder.view,
|
viewHolder.view,
|
||||||
UIUtils.getSelectableBackground(ctx, selectedColor, true)
|
UIUtils.getSelectableBackground(ctx, selectedColor, true)
|
||||||
)
|
)
|
||||||
//set the text for the name
|
//set the text for the name
|
||||||
StringHolder.applyTo(this.getName(), viewHolder.name)
|
StringHolder.applyTo(this.getName(), viewHolder.name)
|
||||||
@ -88,9 +89,9 @@ abstract class CustomUrlBasePrimaryDrawerItem<T, VH : RecyclerView.ViewHolder> :
|
|||||||
viewHolder.name.setTextColor(getTextColorStateList(color, selectedTextColor))
|
viewHolder.name.setTextColor(getTextColorStateList(color, selectedTextColor))
|
||||||
//set the description text color
|
//set the description text color
|
||||||
ColorHolder.applyToOr(
|
ColorHolder.applyToOr(
|
||||||
descriptionTextColor,
|
descriptionTextColor,
|
||||||
viewHolder.description,
|
viewHolder.description,
|
||||||
getTextColorStateList(color, selectedTextColor)
|
getTextColorStateList(color, selectedTextColor)
|
||||||
)
|
)
|
||||||
|
|
||||||
//define the typeface for our textViews
|
//define the typeface for our textViews
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
/* From https://github.com/mikepenz/MaterialDrawer/blob/develop/app/src/main/java/com/mikepenz/materialdrawer/app/drawerItems/CustomUrlPrimaryDrawerItem.java */
|
/* From https://github.com/mikepenz/MaterialDrawer/blob/develop/app/src/main/java/com/mikepenz/materialdrawer/app/drawerItems/CustomUrlPrimaryDrawerItem.java */
|
||||||
package apps.amine.bou.readerforselfoss.utils.drawer
|
package apps.amine.bou.readerforselfoss.utils.drawer
|
||||||
|
|
||||||
import android.support.annotation.LayoutRes
|
import androidx.annotation.LayoutRes
|
||||||
import android.support.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import apps.amine.bou.readerforselfoss.R
|
import apps.amine.bou.readerforselfoss.R
|
||||||
@ -10,7 +10,9 @@ import com.mikepenz.materialdrawer.holder.BadgeStyle
|
|||||||
import com.mikepenz.materialdrawer.holder.StringHolder
|
import com.mikepenz.materialdrawer.holder.StringHolder
|
||||||
import com.mikepenz.materialdrawer.model.interfaces.ColorfulBadgeable
|
import com.mikepenz.materialdrawer.model.interfaces.ColorfulBadgeable
|
||||||
|
|
||||||
class CustomUrlPrimaryDrawerItem : CustomUrlBasePrimaryDrawerItem<CustomUrlPrimaryDrawerItem, CustomUrlPrimaryDrawerItem.ViewHolder>(), ColorfulBadgeable<CustomUrlPrimaryDrawerItem> {
|
class CustomUrlPrimaryDrawerItem :
|
||||||
|
CustomUrlBasePrimaryDrawerItem<CustomUrlPrimaryDrawerItem, CustomUrlPrimaryDrawerItem.ViewHolder>(),
|
||||||
|
ColorfulBadgeable<CustomUrlPrimaryDrawerItem> {
|
||||||
protected var mBadge: StringHolder = StringHolder("")
|
protected var mBadge: StringHolder = StringHolder("")
|
||||||
protected var mBadgeStyle = BadgeStyle()
|
protected var mBadgeStyle = BadgeStyle()
|
||||||
|
|
||||||
@ -64,8 +66,8 @@ class CustomUrlPrimaryDrawerItem : CustomUrlBasePrimaryDrawerItem<CustomUrlPrima
|
|||||||
//style the badge if it is visible
|
//style the badge if it is visible
|
||||||
if (badgeVisible) {
|
if (badgeVisible) {
|
||||||
mBadgeStyle.style(
|
mBadgeStyle.style(
|
||||||
viewHolder.badge,
|
viewHolder.badge,
|
||||||
getTextColorStateList(getColor(ctx), getSelectedTextColor(ctx))
|
getTextColorStateList(getColor(ctx), getSelectedTextColor(ctx))
|
||||||
)
|
)
|
||||||
viewHolder.badgeContainer.visibility = View.VISIBLE
|
viewHolder.badgeContainer.visibility = View.VISIBLE
|
||||||
} else {
|
} else {
|
||||||
|
@ -2,38 +2,38 @@ package apps.amine.bou.readerforselfoss.utils.glide
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory
|
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.request.RequestOptions
|
import com.bumptech.glide.request.RequestOptions
|
||||||
import com.bumptech.glide.request.target.BitmapImageViewTarget
|
import com.bumptech.glide.request.target.BitmapImageViewTarget
|
||||||
|
|
||||||
fun Context.bitmapCenterCrop(url: String, iv: ImageView) =
|
fun Context.bitmapCenterCrop(url: String, iv: ImageView) =
|
||||||
Glide.with(this)
|
Glide.with(this)
|
||||||
.asBitmap()
|
.asBitmap()
|
||||||
.load(url)
|
.load(url)
|
||||||
.apply(RequestOptions.centerCropTransform())
|
.apply(RequestOptions.centerCropTransform())
|
||||||
.into(iv)
|
.into(iv)
|
||||||
|
|
||||||
fun Context.bitmapFitCenter(url: String, iv: ImageView) =
|
fun Context.bitmapFitCenter(url: String, iv: ImageView) =
|
||||||
Glide.with(this)
|
Glide.with(this)
|
||||||
.asBitmap()
|
.asBitmap()
|
||||||
.load(url)
|
.load(url)
|
||||||
.apply(RequestOptions.fitCenterTransform())
|
.apply(RequestOptions.fitCenterTransform())
|
||||||
.into(iv)
|
.into(iv)
|
||||||
|
|
||||||
fun Context.circularBitmapDrawable(url: String, iv: ImageView) =
|
fun Context.circularBitmapDrawable(url: String, iv: ImageView) =
|
||||||
Glide.with(this)
|
Glide.with(this)
|
||||||
.asBitmap()
|
.asBitmap()
|
||||||
.load(url)
|
.load(url)
|
||||||
.apply(RequestOptions.centerCropTransform())
|
.apply(RequestOptions.centerCropTransform())
|
||||||
.into(object : BitmapImageViewTarget(iv) {
|
.into(object : BitmapImageViewTarget(iv) {
|
||||||
override fun setResource(resource: Bitmap?) {
|
override fun setResource(resource: Bitmap?) {
|
||||||
val circularBitmapDrawable = RoundedBitmapDrawableFactory.create(
|
val circularBitmapDrawable = RoundedBitmapDrawableFactory.create(
|
||||||
resources,
|
resources,
|
||||||
resource
|
resource
|
||||||
)
|
)
|
||||||
circularBitmapDrawable.isCircular = true
|
circularBitmapDrawable.isCircular = true
|
||||||
iv.setImageDrawable(circularBitmapDrawable)
|
iv.setImageDrawable(circularBitmapDrawable)
|
||||||
}
|
}
|
||||||
})
|
})
|
@ -23,9 +23,9 @@ class SelfSignedGlideModule : GlideModule {
|
|||||||
val client = getUnsafeHttpClient().build()
|
val client = getUnsafeHttpClient().build()
|
||||||
|
|
||||||
registry?.append(
|
registry?.append(
|
||||||
GlideUrl::class.java,
|
GlideUrl::class.java,
|
||||||
InputStream::class.java,
|
InputStream::class.java,
|
||||||
com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader.Factory(client)
|
com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader.Factory(client)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
package apps.amine.bou.readerforselfoss.utils.network
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.net.ConnectivityManager
|
||||||
|
import android.net.NetworkInfo
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.TextView
|
||||||
|
import apps.amine.bou.readerforselfoss.R
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
|
||||||
|
var snackBarShown = false
|
||||||
|
var view: View? = null
|
||||||
|
|
||||||
|
fun Context.isNetworkAccessible(v: View?): Boolean {
|
||||||
|
val cm = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||||
|
val activeNetwork: NetworkInfo? = cm.activeNetworkInfo
|
||||||
|
val networkIsAccessible = activeNetwork != null && activeNetwork.isConnectedOrConnecting
|
||||||
|
|
||||||
|
if (v != null && !networkIsAccessible && (!snackBarShown || v != view)) {
|
||||||
|
view = v
|
||||||
|
val s = Snackbar
|
||||||
|
.make(
|
||||||
|
v,
|
||||||
|
R.string.no_network_connectivity,
|
||||||
|
Snackbar.LENGTH_INDEFINITE
|
||||||
|
)
|
||||||
|
|
||||||
|
s.setAction(android.R.string.ok) {
|
||||||
|
snackBarShown = false
|
||||||
|
s.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
val view = s.view
|
||||||
|
val tv: TextView = view.findViewById(com.google.android.material.R.id.snackbar_text)
|
||||||
|
tv.setTextColor(Color.WHITE)
|
||||||
|
s.show()
|
||||||
|
snackBarShown = true
|
||||||
|
}
|
||||||
|
return networkIsAccessible
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
package apps.amine.bou.readerforselfoss.utils.persistence
|
||||||
|
|
||||||
|
import apps.amine.bou.readerforselfoss.api.selfoss.Item
|
||||||
|
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossTagType
|
||||||
|
import apps.amine.bou.readerforselfoss.api.selfoss.Source
|
||||||
|
import apps.amine.bou.readerforselfoss.api.selfoss.Tag
|
||||||
|
import apps.amine.bou.readerforselfoss.persistence.entities.ItemEntity
|
||||||
|
import apps.amine.bou.readerforselfoss.persistence.entities.SourceEntity
|
||||||
|
import apps.amine.bou.readerforselfoss.persistence.entities.TagEntity
|
||||||
|
|
||||||
|
fun TagEntity.toView(): Tag =
|
||||||
|
Tag(
|
||||||
|
this.tag,
|
||||||
|
this.color,
|
||||||
|
this.unread
|
||||||
|
)
|
||||||
|
|
||||||
|
fun SourceEntity.toView(): Source =
|
||||||
|
Source(
|
||||||
|
this.id,
|
||||||
|
this.title,
|
||||||
|
SelfossTagType(this.tags),
|
||||||
|
this.spout,
|
||||||
|
this.error,
|
||||||
|
this.icon
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Source.toEntity(): SourceEntity =
|
||||||
|
SourceEntity(
|
||||||
|
this.id,
|
||||||
|
this.title,
|
||||||
|
this.tags.tags,
|
||||||
|
this.spout,
|
||||||
|
this.error,
|
||||||
|
this.icon.orEmpty()
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Tag.toEntity(): TagEntity =
|
||||||
|
TagEntity(
|
||||||
|
this.tag,
|
||||||
|
this.color,
|
||||||
|
this.unread
|
||||||
|
)
|
||||||
|
|
||||||
|
fun ItemEntity.toView(): Item =
|
||||||
|
Item(
|
||||||
|
this.id,
|
||||||
|
this.datetime,
|
||||||
|
this.title,
|
||||||
|
this.content,
|
||||||
|
this.unread,
|
||||||
|
this.starred,
|
||||||
|
this.thumbnail,
|
||||||
|
this.icon,
|
||||||
|
this.link,
|
||||||
|
this.sourcetitle,
|
||||||
|
SelfossTagType(this.tags)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Item.toEntity(): ItemEntity =
|
||||||
|
ItemEntity(
|
||||||
|
this.id,
|
||||||
|
this.datetime,
|
||||||
|
this.title,
|
||||||
|
this.content,
|
||||||
|
this.unread,
|
||||||
|
this.starred,
|
||||||
|
this.thumbnail,
|
||||||
|
this.icon,
|
||||||
|
this.link,
|
||||||
|
this.sourcetitle,
|
||||||
|
this.tags.tags
|
||||||
|
)
|
After Width: | Height: | Size: 206 B |
BIN
app/src/main/res/drawable-hdpi/ic_cloud_download.png
Normal file
After Width: | Height: | Size: 334 B |
BIN
app/src/main/res/drawable-hdpi/ic_fiber_new.png
Normal file
After Width: | Height: | Size: 324 B |
BIN
app/src/main/res/drawable-hdpi/ic_history.png
Normal file
After Width: | Height: | Size: 551 B |
After Width: | Height: | Size: 523 B |
After Width: | Height: | Size: 134 B |
BIN
app/src/main/res/drawable-mdpi/ic_cloud_download.png
Normal file
After Width: | Height: | Size: 228 B |
BIN
app/src/main/res/drawable-mdpi/ic_fiber_new.png
Normal file
After Width: | Height: | Size: 215 B |
BIN
app/src/main/res/drawable-mdpi/ic_history.png
Normal file
After Width: | Height: | Size: 352 B |
After Width: | Height: | Size: 361 B |
After Width: | Height: | Size: 174 B |
BIN
app/src/main/res/drawable-xhdpi/ic_cloud_download.png
Normal file
After Width: | Height: | Size: 380 B |
BIN
app/src/main/res/drawable-xhdpi/ic_fiber_new.png
Normal file
After Width: | Height: | Size: 327 B |
BIN
app/src/main/res/drawable-xhdpi/ic_history.png
Normal file
After Width: | Height: | Size: 684 B |
After Width: | Height: | Size: 660 B |
After Width: | Height: | Size: 255 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_cloud_download.png
Normal file
After Width: | Height: | Size: 547 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_fiber_new.png
Normal file
After Width: | Height: | Size: 490 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_history.png
Normal file
After Width: | Height: | Size: 986 B |
After Width: | Height: | Size: 982 B |
After Width: | Height: | Size: 311 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_cloud_download.png
Normal file
After Width: | Height: | Size: 678 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_fiber_new.png
Normal file
After Width: | Height: | Size: 567 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_history.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.2 KiB |
@ -1,4 +0,0 @@
|
|||||||
<vector android:height="24dp" android:viewportHeight="24.0"
|
|
||||||
android:viewportWidth="24.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<path android:fillColor="#FFFFFF" android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/>
|
|
||||||
</vector>
|
|
@ -1,4 +0,0 @@
|
|||||||
<vector android:height="24dp" android:viewportHeight="24.0"
|
|
||||||
android:viewportWidth="24.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<path android:fillColor="#FFFFFF" android:pathData="M1,21h4L5,9L1,9v12zM23,10c0,-1.1 -0.9,-2 -2,-2h-6.31l0.95,-4.57 0.03,-0.32c0,-0.41 -0.17,-0.79 -0.44,-1.06L14.17,1 7.59,7.59C7.22,7.95 7,8.45 7,9v10c0,1.1 0.9,2 2,2h9c0.83,0 1.54,-0.5 1.84,-1.22l3.02,-7.05c0.09,-0.23 0.14,-0.47 0.14,-0.73v-1.91l-0.01,-0.01L23,10z"/>
|
|
||||||
</vector>
|
|
@ -1,36 +1,37 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout
|
<ScrollView xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context="apps.amine.bou.readerforselfoss.AddSourceActivity"
|
tools:context="apps.amine.bou.readerforselfoss.AddSourceActivity">
|
||||||
android:orientation="vertical">
|
<LinearLayout
|
||||||
<android.support.design.widget.AppBarLayout
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<android.support.v7.widget.Toolbar
|
|
||||||
android:id="@+id/toolbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="?attr/actionBarSize"
|
|
||||||
app:theme="@style/ToolBarStyle"
|
|
||||||
app:popupTheme="?attr/toolbarPopupTheme" />
|
|
||||||
|
|
||||||
</android.support.design.widget.AppBarLayout>
|
|
||||||
|
|
||||||
|
|
||||||
<android.support.constraint.ConstraintLayout
|
|
||||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
|
||||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
|
||||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
|
||||||
android:paddingTop="@dimen/activity_vertical_margin"
|
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_width="match_parent">
|
android:orientation="vertical">
|
||||||
<android.support.constraint.ConstraintLayout
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.Toolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
app:theme="@style/ToolBarStyle"
|
||||||
|
app:popupTheme="?attr/toolbarPopupTheme" />
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||||
|
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||||
|
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||||
|
android:paddingTop="@dimen/activity_vertical_margin"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:layout_width="match_parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
@ -119,19 +120,20 @@
|
|||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
android:layout_marginBottom="16dp"
|
android:layout_marginBottom="16dp"
|
||||||
app:layout_constraintVertical_bias="0.0"/>
|
app:layout_constraintVertical_bias="0.0"/>
|
||||||
</android.support.constraint.ConstraintLayout>
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
style="?android:attr/progressBarStyleLarge"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:id="@+id/progress"
|
android:id="@+id/progress"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
style="?android:attr/progressBarStyleLarge"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="visible"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
android:visibility="visible"/>
|
tools:visibility="gone" />
|
||||||
|
|
||||||
</android.support.constraint.ConstraintLayout>
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
||||||
</LinearLayout>
|
|
@ -30,13 +30,13 @@
|
|||||||
app:prompt_view_background_color="?attr/colorAccent"
|
app:prompt_view_background_color="?attr/colorAccent"
|
||||||
app:prompt_view_thanks_display_time_ms="2000"/>
|
app:prompt_view_thanks_display_time_ms="2000"/>
|
||||||
|
|
||||||
<android.support.design.widget.CoordinatorLayout
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
android:id="@+id/coordLayout"
|
android:id="@+id/coordLayout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_below="@id/promptView">
|
android:layout_below="@id/promptView">
|
||||||
|
|
||||||
<android.support.design.widget.CoordinatorLayout
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
android:id="@+id/intern_coordLayout"
|
android:id="@+id/intern_coordLayout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
@ -46,18 +46,18 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<android.support.design.widget.AppBarLayout
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<android.support.v7.widget.Toolbar
|
<androidx.appcompat.widget.Toolbar
|
||||||
android:id="@+id/toolBar"
|
android:id="@+id/toolBar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="?attr/actionBarSize"
|
android:layout_height="?attr/actionBarSize"
|
||||||
app:theme="@style/ToolBarStyle"
|
app:theme="@style/ToolBarStyle"
|
||||||
app:popupTheme="?attr/toolbarPopupTheme" />
|
app:popupTheme="?attr/toolbarPopupTheme" />
|
||||||
|
|
||||||
</android.support.design.widget.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/drawer_layout"
|
android:id="@+id/drawer_layout"
|
||||||
@ -66,7 +66,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<android.support.v4.widget.SwipeRefreshLayout
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
android:id="@+id/swipeRefreshLayout"
|
android:id="@+id/swipeRefreshLayout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
@ -86,28 +86,30 @@
|
|||||||
android:text="@string/nothing_here"
|
android:text="@string/nothing_here"
|
||||||
android:textAlignment="center"
|
android:textAlignment="center"
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Headline"
|
android:textAppearance="@style/TextAppearance.AppCompat.Headline"
|
||||||
android:background="@color/transparent"
|
android:background="@android:color/transparent"
|
||||||
android:visibility="gone" />
|
android:visibility="gone" />
|
||||||
<android.support.v7.widget.RecyclerView
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/recyclerView"
|
android:id="@+id/recyclerView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@color/transparent"
|
android:background="@android:color/transparent"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
|
android:paddingBottom="60dp"
|
||||||
android:scrollbars="vertical"
|
android:scrollbars="vertical"
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</android.support.v4.widget.SwipeRefreshLayout>
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</android.support.design.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
<com.ashokvarma.bottomnavigation.BottomNavigationBar
|
<com.ashokvarma.bottomnavigation.BottomNavigationBar
|
||||||
android:layout_gravity="bottom"
|
android:layout_gravity="bottom"
|
||||||
android:id="@+id/bottomBar"
|
android:id="@+id/bottomBar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="60dp"/>
|
android:layout_height="60dp"/>
|
||||||
</android.support.design.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|