Compare commits
114 Commits
Author | SHA1 | Date | |
---|---|---|---|
8e9fd9c985 | |||
72400f71c0 | |||
1151587951 | |||
abcd500045 | |||
beda24e736 | |||
37b2c5c2df | |||
7b5246ebf1 | |||
d6d5e72f48 | |||
fa8e88d489 | |||
1ef2da9f76 | |||
1ebd894be7 | |||
a9c493d105 | |||
f833d73fab | |||
9e6602f114 | |||
3bdfef9f8b | |||
6f7f475a6b | |||
8fc5fab67b | |||
6927e92396 | |||
c7470396d7 | |||
f21570e2e4 | |||
51f406e20c | |||
9e3fde744e | |||
ccf406ae68 | |||
bc78d1e079 | |||
d151eb261e | |||
0856598cd9 | |||
f0563efc62 | |||
84dfa9a8a5 | |||
8e25489cca | |||
198f95e1ca | |||
7e02fe89ea | |||
819356412c | |||
deb789bc1b | |||
133ba74548 | |||
1461e32643 | |||
f400c3d9ac | |||
7e595a4f74 | |||
18c9c499b2 | |||
24ae115ed4 | |||
7f345558cd | |||
57177cc910 | |||
cea258bc21 | |||
ed9b1c8ba7 | |||
5a79fd89e9 | |||
42a130db08 | |||
320a8d19de | |||
5721506007 | |||
803e8cb2f4 | |||
98492fd0c0 | |||
0b07178577 | |||
07e545079c | |||
95d64dc5e8 | |||
abe546dcda | |||
e6f367acaf | |||
a9b61853b9 | |||
5afc04a630 | |||
1da4cc2782 | |||
c5ebc89e4f | |||
dfc1719cce | |||
0812259470 | |||
e1476c5840 | |||
e30ea28e3f | |||
4a6d3aab7f | |||
8157146498 | |||
94d23888b1 | |||
737fe9bb4a | |||
0051ed2e73 | |||
e0595957e2 | |||
8d09ff7fdb | |||
04feb66b07 | |||
54b2ac7f24 | |||
12356a35fa | |||
12262304ac | |||
c58f97452e | |||
eb3872f7a6 | |||
9fa178d513 | |||
043b184065 | |||
10559bb894 | |||
d0000d66b2 | |||
b447ac738a | |||
faebfc238c | |||
c28fbd37cc | |||
4b8396959d | |||
b39d510e07 | |||
286dda7f80 | |||
7bda896e2d | |||
ba4feeea87 | |||
6f52eae3c6 | |||
40ea8d56e6 | |||
72e562e8a8 | |||
6fa01bfe19 | |||
0ef59c9b91 | |||
d768d2232b | |||
b44a200731 | |||
016815e0d1 | |||
590534e4a6 | |||
7ea9d4e519 | |||
e0ab09f533 | |||
fbe98f1b16 | |||
d0675b8443 | |||
3ea1ed02ae | |||
ba120b1e0b | |||
acf6995c2d | |||
8306860f90 | |||
65974166be | |||
ee8924f986 | |||
170e575465 | |||
b7d5317b10 | |||
f12e7748c5 | |||
69a2418afc | |||
4924ddd172 | |||
1889b43786 | |||
f2e38a4203 | |||
90a8fac8d4 |
43
.github/CONTRIBUTING.md
vendored
@ -10,7 +10,7 @@ Please read the guidelines before contributing, and follow them (or try to) when
|
||||
|
||||
There are many ways to contribute to this project, you could report bugs, request missing features, suggest enhancements and changes to existing ones. You also can improve the README with useful tips that could help the other users.
|
||||
|
||||
You can fork the repository, and [help me solve some issues](https://github.com/aminecmi/ReaderforSelfoss/labels/help%20wanted) or [develop new things](https://github.com/aminecmi/ReaderforSelfoss/issues)
|
||||
You can fork the repository, and [help me solve some issues](https://github.com/aminecmi/ReaderforSelfoss/issues?q=is%3Aissue+is%3Aopen+label%3A%22Up+For+Grabs%22) or [develop new things](https://github.com/aminecmi/ReaderforSelfoss/issues)
|
||||
|
||||
### What I can't help you with.
|
||||
|
||||
@ -22,7 +22,8 @@ Always check if the web version of your instance is working.
|
||||
### Bug reports/Feature request
|
||||
|
||||
* Always search before reporting an issue or asking for a feature to avoid duplicates.
|
||||
* Include every usefull details (app version, phone model, Android version and screenshots when possible)
|
||||
* Include your unique user id. It's displayed on the debug settings page. (You can tap it, it'll be copied to your clipboard)
|
||||
* Include every other useful details (app version, phone model, Android version and screenshots when possible).
|
||||
* Avoid bumping non-fatal issues, or feature requests. I'll try to fix them as soon as possible, and try to prioritize the requests. (You may wan to use the [reactions](https://github.com/blog/2119-add-reactions-to-pull-requests-issues-and-comments) for that)
|
||||
|
||||
### Pull requests
|
||||
@ -30,7 +31,7 @@ Always check if the web version of your instance is working.
|
||||
* Please ask before starting to work on an issue. I may be working on it, or someone else could be doing so.
|
||||
* Each pull request should implement **ONE** feature or bugfix. Keep in mind that you can submit as many PR as you want.
|
||||
* Your code must be simple and clear enough to avoid using comments to explain what it does.
|
||||
* Follow the used coding style [the official one](https://kotlinlang.org/docs/reference/coding-conventions.html) ([some idoms for reference](http://kotlinlang.org/docs/reference/idioms.html)) with more to come.
|
||||
* Follow the used coding style [the android koding style](https://android.github.io/kotlin-guides/style.html) ([some idoms for reference](http://kotlinlang.org/docs/reference/idioms.html)) with more to come.
|
||||
* Try as much as possible to write a test for your feature, and if you do so, run it, and make it work.
|
||||
* Always check your changes and discard the ones that are irrelevant to your feature or bugfix.
|
||||
* Have meaningful commit messages.
|
||||
@ -38,18 +39,40 @@ Always check if the web version of your instance is working.
|
||||
* Be willing to accept criticism on your PRs (as I am on mine).
|
||||
* Remember that PR review can take time.
|
||||
|
||||
|
||||
# Build the project
|
||||
|
||||
You can directly import this project into IntellIJ/Android Studio.
|
||||
|
||||
You'll have to:
|
||||
|
||||
- [Create your own launcher icon](https://developer.android.com/studio/write/image-asset-studio.html#creating-launcher)
|
||||
- Configure fabric and add your `apiKey` and `apiSecret` in the `fabric.properties` file.
|
||||
- Create a firebase project and add the `google-services.json` to the `app/` folder.
|
||||
- Define some parameters either in `~/.gradle/gradle.properties` or as gradle parameters (see the examples)
|
||||
|
||||
- Configure Fabric, or [remove it](https://docs.fabric.io/android/fabric/settings/removing.html#).
|
||||
- Define the following in `res/values/strings.xml` or create `res/values/secrets.xml`
|
||||
- mercuryApiKey: A [Mercury](https://mercury.postlight.com/web-parser/) web parser api key for the internal browser
|
||||
- feedbackEmail: An email to receive users feedback.
|
||||
- sourceUrl: an url to the source code, used in the settings. **It can be empty.**
|
||||
- trackerUrl: an url to the tracker, used in the settings. **It can be empty.**
|
||||
- githubToken: a github token used to report issues from within the app. [Details here](https://github.com/heinrichreimer/android-issue-reporter#how-to-create-a-bot-key). **It can be empty.**
|
||||
- appLoginUrl, appLoginUsername and appLoginPassword: url, username and password of a selfoss instance. **These are only used for tests. They can be empty if you don't test API calls.**
|
||||
|
||||
- mercury: A [Mercury](https://mercury.postlight.com/web-parser/) web parser api key for the internal browser
|
||||
- feedback_email: An email to receive users feedback.
|
||||
- source_url: an url to the source code, used in the settings
|
||||
- tracker_url: an url to the tracker, used in the settings
|
||||
### Examples:
|
||||
#### Inside ~/.gradle/gradle.properties
|
||||
|
||||
```
|
||||
appLoginUrl="URL" # It can be empty.
|
||||
appLoginUsername="LOGIN" # It can be empty.
|
||||
appLoginPassword="PASS" # It can be empty.
|
||||
mercuryApiKey="LONGAPIKEY"
|
||||
feedbackEmail="EMAIL"
|
||||
sourceUrl="URLSOURCE" # It can be empty.
|
||||
trackerUrl="URLTRACKER" # It can be empty.
|
||||
githubToken="GITHUBTOKEN" # It can be empty or use https://github.com/heinrichreimer/android-issue-reporter#how-to-create-a-bot-key to generate one
|
||||
```
|
||||
|
||||
#### As gradle parameters
|
||||
|
||||
```
|
||||
./gradlew .... -P appLoginUrl="URL" -P appLoginUsername="LOGIN" -P appLoginPassword="PASS" -P mercuryApiKey="LONGAPIKEY" -P feedbackEmail="EMAIL" -P sourceUrl="URLSOURCE" -P trackerUrl="URLTRACKER" -P githubToken="GITHUBTOKEN"
|
||||
```
|
||||
|
5
.gitignore
vendored
@ -214,7 +214,6 @@ gradle-app.setting
|
||||
|
||||
# End of https://www.gitignore.io/api/java,gradle,android,androidstudio
|
||||
|
||||
secrets.xml
|
||||
release/
|
||||
|
||||
mipmap-*
|
||||
release/
|
||||
crowdin.properties
|
199
CHANGELOG.md
@ -1,3 +1,200 @@
|
||||
**1.5.4.12**
|
||||
|
||||
- Fixed fab and toolbar issue (#113)
|
||||
|
||||
- Fixed links clickable (#114)
|
||||
|
||||
- Changed the link colors in the article viewer
|
||||
|
||||
**1.5.4.11**
|
||||
|
||||
- Hiding FABs on scroll.
|
||||
|
||||
- Closing #109 (code cleaning)
|
||||
|
||||
- Hiding fabs on scroll (#101)
|
||||
|
||||
**1.5.4.10**
|
||||
|
||||
- Displaying a loader when "reading more" in the article viewer.
|
||||
|
||||
- Displaying the thumbnail instead of icon on the article viewer.
|
||||
|
||||
- Scrolling to top when loading content with the "read more" button.
|
||||
|
||||
**1.5.4.09**
|
||||
|
||||
- Using the kotlin wrapper for the material drawer (see #98 for more details).
|
||||
|
||||
- Updated support libraries
|
||||
|
||||
- Changed the Floating Action Button to the support library version.
|
||||
|
||||
- New reader activity action bar #103.
|
||||
|
||||
**1.5.4.08**
|
||||
|
||||
- Thanks @jrafaelsantana for translating the whole app in Brazilian Portuguese.
|
||||
|
||||
**1.5.4.07**
|
||||
|
||||
- Loading more items on swipe too.
|
||||
|
||||
- Fixed popup menu style. User may need to reselect the theme.
|
||||
|
||||
- Disabled reporting marking items as read if there isn't an issue.
|
||||
|
||||
**1.5.4.05/06**
|
||||
|
||||
- Translation fix.
|
||||
|
||||
**1.5.4.04**
|
||||
|
||||
- Fixing an issue with marking items as read (something related to an old version of selfoss).
|
||||
|
||||
**1.5.4.03**
|
||||
|
||||
- Trying to fix some issue with pre-launch reports. Reverted because it seems to be related to the dev console side.
|
||||
|
||||
**1.5.4.02**
|
||||
|
||||
- Fixing full height cards issue.
|
||||
|
||||
**1.5.4.01**
|
||||
|
||||
- Removed the "apk downloaded from outside of playstore" message.
|
||||
|
||||
- Versions update.
|
||||
|
||||
- HTML viewer version update. It should fix an issue with images.
|
||||
|
||||
- Some code cleaning.
|
||||
|
||||
**1.5.4.00**
|
||||
|
||||
- Added issue reporting from within the app.
|
||||
|
||||
**1.5.3.06**
|
||||
|
||||
- Fixed infinite scroll not working.
|
||||
|
||||
- Fixed logs not working.
|
||||
|
||||
- Temporary workaround handling opening invalid urls. Waiting to solve #83.
|
||||
|
||||
**1.5.3.05**
|
||||
|
||||
- Fixed an issue on older versions of Android.
|
||||
|
||||
- Libs update.
|
||||
|
||||
**1.5.3.04**
|
||||
|
||||
- Crowdin translations
|
||||
|
||||
**1.5.3.03**
|
||||
|
||||
- Libs updates.
|
||||
|
||||
- Translation fix.
|
||||
|
||||
**1.5.3.01/02**
|
||||
|
||||
- Added translation link to the settings page.
|
||||
|
||||
- Added the translation link to the README.
|
||||
|
||||
**1.5.3.00**
|
||||
|
||||
- (BETA) Added pull from bottom to load more pages of results. May be buggy.
|
||||
|
||||
**1.5.2.18/19**
|
||||
|
||||
- APK minification finally working. That means less space taken !
|
||||
- Added an option to log every API call.
|
||||
|
||||
**1.5.2.17**
|
||||
|
||||
- Source code and tracker links weren't being set, and updated the contributing doc.
|
||||
|
||||
**1.5.2.15/16**
|
||||
|
||||
- Adding an account header on the lateral drawer.
|
||||
|
||||
- The account header is only displayed when the setting is enabled.
|
||||
|
||||
**1.5.2.13/14**
|
||||
|
||||
- Updated glide.
|
||||
|
||||
- Loading images from self signed certificate now working.
|
||||
|
||||
**1.5.2.12**
|
||||
|
||||
- Self signed certificates are now working for loading data. Image are not loading yet.
|
||||
|
||||
**1.5.2.11**
|
||||
|
||||
- Added a random unique identifier to be used in the logs.
|
||||
|
||||
**1.5.2.08/09/10**
|
||||
|
||||
- Added settable logs for reading articles problems.
|
||||
|
||||
**1.5.2.07**
|
||||
|
||||
- Added the ability to choose the number of items loaded (the maximum value is 200 and is imposed by the selfoss api)
|
||||
|
||||
**1.5.2.06**
|
||||
|
||||
- Fix problem introduced in 1.5.2.04. SVG file not working on older versions of android.
|
||||
|
||||
**1.5.2.05**
|
||||
|
||||
- Versions updates
|
||||
|
||||
**1.5.2.04**
|
||||
|
||||
- Reverted to the old icon.
|
||||
|
||||
- Better icon for the intro activity.
|
||||
|
||||
- Updated gradle version.
|
||||
|
||||
**1.5.2.03**
|
||||
|
||||
- Added the ability to accept self signed certificates. (Needs more testing)
|
||||
|
||||
**1.5.2.02**
|
||||
|
||||
- Added optional login option.
|
||||
|
||||
**1.5.2.01**
|
||||
|
||||
- New (Better) Icon !
|
||||
|
||||
**1.5.2.0**
|
||||
|
||||
- New Icon !
|
||||
|
||||
**1.5.1.9/10/11**
|
||||
|
||||
- Hiding the unread badge when marking all items as read.
|
||||
|
||||
**1.5.1.8**
|
||||
|
||||
- Fixes and libs updates.
|
||||
|
||||
**1.5.1.7**
|
||||
|
||||
- Bug fixes.
|
||||
|
||||
- Code cleaning
|
||||
|
||||
**1.5.1.6**
|
||||
|
||||
- Added back the badges after it was fixed on the library side.
|
||||
|
||||
**1.5.1.5**
|
||||
|
||||
- THEMES !!!! For now, the app has predefined themes. You can ask for new ones until I make them dynamic.
|
||||
@ -192,4 +389,4 @@ _Updates_
|
||||
|
||||
**1.3.3.4**
|
||||
|
||||
...
|
||||
...
|
||||
|
10
README.md
@ -1,10 +1,8 @@
|
||||
# ReaderForSelfoss
|
||||
|
||||
[](https://gitter.im/amine-bou/ReaderForSelfoss)
|
||||
[](https://crowdin.com/project/readerforselfoss) [](https://gitter.im/amine-bou/ReaderForSelfoss)
|
||||
|
||||
[](https://circleci.com/gh/aminecmi/ReaderforSelfoss/tree/master)
|
||||
|
||||
[](https://codebeat.co/projects/github-com-aminecmi-readerforselfoss-master)
|
||||
[](http://jenkins.amine-bou.fr/job/ReaderForSelfoss/)
|
||||
|
||||
[](https://www.codetriage.com/aminecmi/readerforselfoss)
|
||||
|
||||
@ -12,6 +10,8 @@ This is the repo of [Reader For Selfoss](https://play.google.com/store/apps/deta
|
||||
|
||||
It's an RSS Reader for Android, that **only** works with [Selfoss](https://selfoss.aditu.de/)
|
||||
|
||||
The last APK built from source is available [here](https://jenkins.amine-bou.fr/job/ReaderForSelfoss/lastSuccessfulBuild/artifact/SignApksBuilder-out/selfoss-key/selfoss/app-githubConfig-release-unsigned.apk/app-githubConfig-release.apk).
|
||||
|
||||
|
||||
## Want to help ?
|
||||
|
||||
@ -22,3 +22,5 @@ Check the [Contribution guide](https://github.com/aminecmi/ReaderforSelfoss/blob
|
||||
- [Check what changed](https://github.com/aminecmi/ReaderforSelfoss/blob/master/CHANGELOG.md)
|
||||
- [See what I'm doing](https://github.com/aminecmi/ReaderforSelfoss/projects/1)
|
||||
- [Create an issue, or request a new feature](https://github.com/aminecmi/ReaderforSelfoss/issues)
|
||||
- [Help translation the app](https://crowdin.com/project/readerforselfoss)
|
||||
- [Ask for help](https://gitter.im/amine-bou/ReaderForSelfoss)
|
||||
|
119
app/build.gradle
@ -8,25 +8,46 @@ buildscript {
|
||||
}
|
||||
}
|
||||
|
||||
def gitVersion() {
|
||||
def process = "git describe --abbrev=0 --tags".execute()
|
||||
return process.text.substring(1).replaceAll("\\.", "")
|
||||
}
|
||||
|
||||
def versionCodeFromGit() {
|
||||
println "version code " + gitVersion().toInteger()
|
||||
return gitVersion().toInteger()
|
||||
}
|
||||
|
||||
def versionNameFromGit() {
|
||||
println "version code " + gitVersion().trim()
|
||||
return gitVersion().trim()
|
||||
}
|
||||
|
||||
apply plugin: 'org.sonarqube'
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
apply plugin: 'io.fabric'
|
||||
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
|
||||
repositories {
|
||||
maven { url 'https://maven.fabric.io/public' }
|
||||
maven {
|
||||
url 'https://maven.fabric.io/public'
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 26
|
||||
buildToolsVersion "26.0.0"
|
||||
compileSdkVersion 27
|
||||
buildToolsVersion '27.0.0'
|
||||
defaultConfig {
|
||||
applicationId "apps.amine.bou.readerforselfoss"
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 25
|
||||
versionCode 1515
|
||||
versionName "1.5.1.5"
|
||||
targetSdkVersion 27
|
||||
versionCode versionCodeFromGit()
|
||||
versionName versionNameFromGit()
|
||||
|
||||
// Enabling multidex support.
|
||||
multiDexEnabled true
|
||||
@ -38,10 +59,18 @@ android {
|
||||
|
||||
// tests
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
|
||||
buildConfigField "String", "MERCURY_KEY", mercuryApiKey
|
||||
buildConfigField "String", "FEEDBACK_EMAIL", feedbackEmail
|
||||
buildConfigField "String", "SOURCE_URL", sourceUrl
|
||||
buildConfigField "String", "TRACKER_URL", trackerUrl
|
||||
buildConfigField "String", "TRANSLATION_URL", translationUrl
|
||||
buildConfigField "String", "GITHUB_TOKEN", githubToken
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'),
|
||||
'proguard-rules.pro'
|
||||
}
|
||||
@ -68,77 +97,79 @@ android {
|
||||
|
||||
dependencies {
|
||||
// Testing
|
||||
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
|
||||
androidTestCompile 'com.android.support.test:runner:0.5'
|
||||
androidTestCompile 'com.android.support.test.espresso:espresso-core:3.0.1'
|
||||
androidTestCompile 'com.android.support.test:runner:1.0.1'
|
||||
// Espresso-contrib for DatePicker, RecyclerView, Drawer actions, Accessibility checks, CountingIdlingResource
|
||||
androidTestCompile 'com.android.support.test.espresso:espresso-contrib:2.2.2'
|
||||
androidTestCompile 'com.android.support.test.espresso:espresso-contrib:3.0.1'
|
||||
// Espresso-intents for validation and stubbing of Intents
|
||||
androidTestCompile 'com.android.support.test.espresso:espresso-intents:2.2.2'
|
||||
androidTestCompile 'com.android.support.test.espresso:espresso-intents:3.0.1'
|
||||
|
||||
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
|
||||
|
||||
// Android Support
|
||||
compile 'com.android.support:appcompat-v7:26.0.0-beta2'
|
||||
compile 'com.android.support:design:26.0.0-beta2'
|
||||
compile 'com.android.support:recyclerview-v7:26.0.0-beta2'
|
||||
compile 'com.android.support:support-v4:26.0.0-beta2'
|
||||
compile 'com.android.support:support-vector-drawable:26.0.0-beta2'
|
||||
compile 'com.android.support:customtabs:26.0.0-beta2'
|
||||
compile 'com.android.support:cardview-v7:26.0.0-beta2'
|
||||
compile 'com.android.support:appcompat-v7:27.0.0'
|
||||
compile 'com.android.support:design:27.0.0'
|
||||
compile 'com.android.support:recyclerview-v7:27.0.0'
|
||||
compile 'com.android.support:support-v4:27.0.0'
|
||||
compile 'com.android.support:support-vector-drawable:27.0.0'
|
||||
compile 'com.android.support:customtabs:27.0.0'
|
||||
compile 'com.android.support:cardview-v7:27.0.0'
|
||||
compile 'com.android.support.constraint:constraint-layout:1.0.2'
|
||||
|
||||
// Firebase + crashlytics
|
||||
compile 'com.google.firebase:firebase-core:11.0.1'
|
||||
compile 'com.google.firebase:firebase-config:11.0.1'
|
||||
compile 'com.google.firebase:firebase-invites:11.0.1'
|
||||
compile('com.crashlytics.sdk.android:crashlytics:2.6.8@aar') {
|
||||
transitive = true
|
||||
compile 'com.google.firebase:firebase-core:11.4.2'
|
||||
compile 'com.google.firebase:firebase-config:11.4.2'
|
||||
compile 'com.google.firebase:firebase-invites:11.4.2'
|
||||
compile('com.crashlytics.sdk.android:crashlytics:2.7.1@aar') {
|
||||
transitive = true;
|
||||
}
|
||||
|
||||
//multidex
|
||||
compile 'com.android.support:multidex:1.0.1'
|
||||
compile 'com.android.support:multidex:1.0.2'
|
||||
|
||||
// Intro
|
||||
compile 'agency.tango.android:material-intro-screen:0.0.5'
|
||||
|
||||
// About
|
||||
compile('com.mikepenz:aboutlibraries:5.9.6@aar') {
|
||||
compile('com.mikepenz:aboutlibraries:6.0.0@aar') {
|
||||
transitive = true
|
||||
}
|
||||
|
||||
// Retrofit + http logging + okhttp
|
||||
compile 'com.squareup.retrofit2:retrofit:2.3.0'
|
||||
compile 'com.squareup.okhttp3:logging-interceptor:3.8.0'
|
||||
compile 'com.squareup.okhttp3:logging-interceptor:3.9.0'
|
||||
compile 'com.squareup.retrofit2:converter-gson:2.3.0'
|
||||
compile 'com.burgstaller:okhttp-digest:1.12'
|
||||
|
||||
// Material-ish things
|
||||
compile 'com.ashokvarma.android:bottom-navigation-bar:2.0.1'
|
||||
compile 'com.melnykov:floatingactionbutton:1.3.0'
|
||||
compile 'com.ashokvarma.android:bottom-navigation-bar:2.0.3'
|
||||
compile 'com.github.jd-alexander:LikeButton:0.2.1'
|
||||
compile 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
|
||||
compile 'org.sufficientlysecure:html-textview:3.3'
|
||||
compile 'org.sufficientlysecure:html-textview:3.5'
|
||||
|
||||
// glide
|
||||
compile 'com.github.bumptech.glide:glide:3.7.0'
|
||||
compile 'com.github.bumptech.glide:glide:4.1.1'
|
||||
compile 'com.github.bumptech.glide:okhttp3-integration:4.1.1'
|
||||
|
||||
// Asking politely users to rate the app
|
||||
compile 'com.github.stkent:amplify:1.5.0'
|
||||
compile 'com.github.stkent:amplify:2.1.0'
|
||||
|
||||
// For the article reader
|
||||
compile 'com.klinkerapps:drag-dismiss-activity:1.4.1'
|
||||
compile 'com.klinkerapps:drag-dismiss-activity:1.5.0'
|
||||
|
||||
// Drawer
|
||||
compile('com.mikepenz:materialdrawer:5.9.3@aar') {
|
||||
transitive = true
|
||||
}
|
||||
implementation 'co.zsmb:materialdrawer-kt:1.2.1'
|
||||
compile 'com.anupcowkur:reservoir:3.1.0'
|
||||
|
||||
// Themes
|
||||
compile 'com.52inc:scoops:1.0.0'
|
||||
|
||||
// Github issues reporter
|
||||
compile 'com.heinrichreimersoftware:android-issue-reporter:1.3.1'
|
||||
|
||||
compile 'com.github.rubensousa:floatingtoolbar:1.5.1'
|
||||
}
|
||||
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
@ -147,6 +178,7 @@ apply plugin: 'com.google.gms.google-services'
|
||||
afterEvaluate {
|
||||
initFabricPropertiesIfNeeded()
|
||||
initAppLoginPropertiesIfNeeded()
|
||||
initAppForSecretPropertiesIfNeeded()
|
||||
}
|
||||
|
||||
def initFabricPropertiesIfNeeded() {
|
||||
@ -170,4 +202,19 @@ def initAppLoginPropertiesIfNeeded() {
|
||||
entry(key: "appLoginPassword", value: System.getProperty("appLoginPassword"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def initAppForSecretPropertiesIfNeeded() {
|
||||
def propertiesFile = file(System.getProperty("user.home") + '/.gradle/gradle.properties')
|
||||
if (!propertiesFile.exists()) {
|
||||
def commentMessage = "This is autogenerated local property from system environment to prevent key to be committed to source control."
|
||||
ant.propertyfile(file: System.getProperty("user.home") + "/.gradle/gradle.properties", comment: commentMessage) {
|
||||
entry(key: "mercuryApiKey", value: System.getProperty("mercuryApiKey"))
|
||||
entry(key: "feedbackEmail", value: System.getProperty("feedbackEmail"))
|
||||
entry(key: "sourceUrl", value: System.getProperty("sourceUrl"))
|
||||
entry(key: "trackerUrl", value: System.getProperty("trackerUrl"))
|
||||
entry(key: "translationUrl", value: System.getProperty("translationUrl"))
|
||||
entry(key: "githubToken", value: System.getProperty("githubToken"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
15
app/proguard-rules.pro
vendored
@ -56,4 +56,17 @@
|
||||
|
||||
|
||||
#Bottom bar lib
|
||||
-dontwarn com.roughike.bottombar.**
|
||||
-dontwarn com.roughike.bottombar.**
|
||||
|
||||
|
||||
# self signed glidemodule
|
||||
-keep public class * implements com.bumptech.glide.module.GlideModule
|
||||
-keep public class * extends com.bumptech.glide.AppGlideModule
|
||||
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
|
||||
**[] $VALUES;
|
||||
public *;
|
||||
}
|
||||
|
||||
-dontwarn com.anupcowkur.reservoir.**
|
||||
|
||||
-dontwarn javax.annotation.**
|
@ -23,8 +23,8 @@ import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import apps.amine.bou.readerforselfoss.settings.SettingsActivity
|
||||
import apps.amine.bou.readerforselfoss.utils.Config
|
||||
import com.heinrichreimersoftware.androidissuereporter.IssueReporterLauncher
|
||||
import org.junit.After
|
||||
|
||||
|
||||
@ -72,9 +72,6 @@ class HomeActivityEspressoTest {
|
||||
|
||||
onView(withContentDescription(R.string.abc_toolbar_collapse_description)).perform(click())
|
||||
|
||||
|
||||
onView(withMenu(id = R.id.readAll, titleId = R.string.readAll)).perform(click())
|
||||
|
||||
openActionBarOverflowOrOptionsMenu(context)
|
||||
|
||||
onView(withMenu(id = R.id.refresh, titleId = R.string.menu_home_refresh))
|
||||
@ -86,9 +83,6 @@ class HomeActivityEspressoTest {
|
||||
|
||||
intended(hasComponent(LoginActivity::class.java.name), times(1))
|
||||
|
||||
onView(isRoot()).perform(pressBack())
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -98,8 +92,9 @@ class HomeActivityEspressoTest {
|
||||
|
||||
onView(withId(R.id.material_drawer_layout)).perform(DrawerActions.open())
|
||||
|
||||
onView(withText(R.string.action_about)).perform(click())
|
||||
intended(hasComponent(LibsActivity::class.java.name))
|
||||
onView(withText(R.string.drawer_report_bug)).perform(click())
|
||||
intended(hasComponent(IssueReporterLauncher.Activity::class.java.name))
|
||||
onView(isRoot()).perform(pressBack())
|
||||
onView(isRoot()).perform(pressBack())
|
||||
intended(hasComponent(HomeActivity::class.java.name))
|
||||
|
||||
@ -108,10 +103,6 @@ class HomeActivityEspressoTest {
|
||||
onView(withId(R.id.material_drawer_layout)).perform(DrawerActions.open())
|
||||
onView(withText(R.string.drawer_action_clear)).perform(click())
|
||||
|
||||
// bug
|
||||
//onView(withText(R.string.title_activity_settings)).perform(scrollTo(), click())
|
||||
//intended(hasComponent(SettingsActivity::class.java.name))
|
||||
|
||||
}
|
||||
|
||||
// TODO: test articles opening and actions for cards and lists
|
||||
|
@ -91,29 +91,6 @@ class IntroActivityEspressoTest {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun clickSelfossUrl() {
|
||||
rule.launchActivity(Intent())
|
||||
|
||||
onView(withText(R.string.intro_hello_title)).check(matches(isDisplayed()))
|
||||
|
||||
onView(withId(R.id.button_next)).perform(click())
|
||||
|
||||
onView(withId(R.id.button_message)).perform(click())
|
||||
|
||||
intended(
|
||||
allOf(
|
||||
hasData(
|
||||
hasHost(
|
||||
equalTo("selfoss.aditu.de")
|
||||
)
|
||||
),
|
||||
hasAction(Intent.ACTION_VIEW)
|
||||
)
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@After
|
||||
fun releaseIntents() {
|
||||
Intents.release()
|
||||
|
@ -32,18 +32,18 @@ class LoginActivityEspressoTest {
|
||||
@Rule @JvmField
|
||||
val rule = ActivityTestRule(LoginActivity::class.java, true, false)
|
||||
|
||||
lateinit var context: Context
|
||||
lateinit var url: String
|
||||
lateinit var username: String
|
||||
lateinit var password: String
|
||||
private lateinit var context: Context
|
||||
private lateinit var url: String
|
||||
private lateinit var username: String
|
||||
private lateinit var password: String
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
val editor =
|
||||
context
|
||||
.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
|
||||
.edit()
|
||||
context
|
||||
.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
|
||||
.edit()
|
||||
editor.clear()
|
||||
editor.commit()
|
||||
|
||||
@ -77,12 +77,12 @@ class LoginActivityEspressoTest {
|
||||
fun wrongLoginUrl() {
|
||||
rule.launchActivity(Intent())
|
||||
|
||||
onView(withId(R.id.login_progress))
|
||||
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)))
|
||||
onView(withId(R.id.loginProgress))
|
||||
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)))
|
||||
|
||||
onView(withId(R.id.url)).perform(click()).perform(typeText("WRONGURL"))
|
||||
onView(withId(R.id.urlView)).perform(click()).perform(typeText("WRONGURL"))
|
||||
|
||||
onView(withId(R.id.email_sign_in_button)).perform(click())
|
||||
onView(withId(R.id.signInButton)).perform(click())
|
||||
|
||||
onView(withId(R.id.urlLayout)).check(matches(isHintOrErrorEnabled()))
|
||||
}
|
||||
@ -94,24 +94,24 @@ class LoginActivityEspressoTest {
|
||||
|
||||
rule.launchActivity(Intent())
|
||||
|
||||
onView(withId(R.id.url)).perform(click()).perform(typeText(url), closeSoftKeyboard())
|
||||
onView(withId(R.id.urlView)).perform(click()).perform(typeText(url), closeSoftKeyboard())
|
||||
|
||||
onView(withId(R.id.withLogin)).perform(click())
|
||||
|
||||
onView(withId(R.id.email_sign_in_button)).perform(click())
|
||||
onView(withId(R.id.signInButton)).perform(click())
|
||||
|
||||
onView(withId(R.id.loginLayout)).check(matches(isHintOrErrorEnabled()))
|
||||
onView(withId(R.id.passwordLayout)).check(matches(isHintOrErrorEnabled()))
|
||||
|
||||
onView(withId(R.id.login)).perform(click()).perform(typeText(username), closeSoftKeyboard())
|
||||
onView(withId(R.id.loginView)).perform(click()).perform(typeText(username), closeSoftKeyboard())
|
||||
|
||||
onView(withId(R.id.passwordLayout)).check(matches(isHintOrErrorEnabled()))
|
||||
|
||||
onView(withId(R.id.email_sign_in_button)).perform(click())
|
||||
onView(withId(R.id.signInButton)).perform(click())
|
||||
|
||||
onView(withId(R.id.passwordLayout)).check(
|
||||
matches(
|
||||
isHintOrErrorEnabled())
|
||||
matches(
|
||||
isHintOrErrorEnabled())
|
||||
)
|
||||
|
||||
}
|
||||
@ -121,15 +121,15 @@ class LoginActivityEspressoTest {
|
||||
|
||||
rule.launchActivity(Intent())
|
||||
|
||||
onView(withId(R.id.url)).perform(click()).perform(typeText(url), closeSoftKeyboard())
|
||||
onView(withId(R.id.urlView)).perform(click()).perform(typeText(url), closeSoftKeyboard())
|
||||
|
||||
onView(withId(R.id.withLogin)).perform(click())
|
||||
|
||||
onView(withId(R.id.login)).perform(click()).perform(typeText(username), closeSoftKeyboard())
|
||||
onView(withId(R.id.loginView)).perform(click()).perform(typeText(username), closeSoftKeyboard())
|
||||
|
||||
onView(withId(R.id.password)).perform(click()).perform(typeText("WRONGPASS"), closeSoftKeyboard())
|
||||
onView(withId(R.id.passwordView)).perform(click()).perform(typeText("WRONGPASS"), closeSoftKeyboard())
|
||||
|
||||
onView(withId(R.id.email_sign_in_button)).perform(click())
|
||||
onView(withId(R.id.signInButton)).perform(click())
|
||||
|
||||
onView(withId(R.id.urlLayout)).check(matches(isHintOrErrorEnabled()))
|
||||
onView(withId(R.id.loginLayout)).check(matches(isHintOrErrorEnabled()))
|
||||
@ -142,15 +142,15 @@ class LoginActivityEspressoTest {
|
||||
|
||||
rule.launchActivity(Intent())
|
||||
|
||||
onView(withId(R.id.url)).perform(click()).perform(typeText(url), closeSoftKeyboard())
|
||||
onView(withId(R.id.urlView)).perform(click()).perform(typeText(url), closeSoftKeyboard())
|
||||
|
||||
onView(withId(R.id.withLogin)).perform(click())
|
||||
|
||||
onView(withId(R.id.login)).perform(click()).perform(typeText(username), closeSoftKeyboard())
|
||||
onView(withId(R.id.loginView)).perform(click()).perform(typeText(username), closeSoftKeyboard())
|
||||
|
||||
onView(withId(R.id.password)).perform(click()).perform(typeText(password), closeSoftKeyboard())
|
||||
onView(withId(R.id.passwordView)).perform(click()).perform(typeText(password), closeSoftKeyboard())
|
||||
|
||||
onView(withId(R.id.email_sign_in_button)).perform(click())
|
||||
onView(withId(R.id.signInButton)).perform(click())
|
||||
|
||||
intended(hasComponent(HomeActivity::class.java.name))
|
||||
|
||||
|
@ -28,7 +28,8 @@
|
||||
android:name=".IntroActivity"
|
||||
android:theme="@style/Theme.Intro">
|
||||
</activity>
|
||||
<activity android:name=".LoginActivity"
|
||||
<activity
|
||||
android:name=".LoginActivity"
|
||||
android:label="@string/title_activity_login">
|
||||
</activity>
|
||||
<activity android:name=".HomeActivity">
|
||||
@ -41,13 +42,15 @@
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="apps.amine.bou.readerforselfoss.HomeActivity" />
|
||||
</activity>
|
||||
<activity android:name=".SourcesActivity"
|
||||
<activity
|
||||
android:name=".SourcesActivity"
|
||||
android:parentActivityName=".HomeActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".HomeActivity" />
|
||||
</activity>
|
||||
<activity android:name=".AddSourceActivity"
|
||||
<activity
|
||||
android:name=".AddSourceActivity"
|
||||
android:parentActivityName=".SourcesActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
@ -61,9 +64,13 @@
|
||||
<data android:mimeType="text/plain" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name=".ReaderActivity"
|
||||
android:theme="@style/DragDismissTheme">
|
||||
<activity
|
||||
android:name=".ReaderActivity">
|
||||
</activity>
|
||||
|
||||
<meta-data
|
||||
android:name="apps.amine.bou.readerforselfoss.utils.glide.SelfSignedGlideModule"
|
||||
android:value="GlideModule" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
BIN
app/src/main/ic_launcher-web.png
Normal file
After Width: | Height: | Size: 20 KiB |
@ -2,22 +2,27 @@ package apps.amine.bou.readerforselfoss
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.preference.PreferenceManager
|
||||
import android.support.constraint.ConstraintLayout
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.support.v7.widget.Toolbar
|
||||
import android.view.View
|
||||
import android.widget.*
|
||||
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
|
||||
import android.widget.AdapterView
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.EditText
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.Spinner
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.Spout
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
|
||||
import apps.amine.bou.readerforselfoss.utils.Config
|
||||
import apps.amine.bou.readerforselfoss.utils.isUrlValid
|
||||
import apps.amine.bou.readerforselfoss.utils.isBaseUrlValid
|
||||
import com.ftinc.scoop.Scoop
|
||||
import kotlinx.android.synthetic.main.activity_add_source.*
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
|
||||
|
||||
class AddSourceActivity : AppCompatActivity() {
|
||||
@ -28,39 +33,43 @@ class AddSourceActivity : AppCompatActivity() {
|
||||
super.onCreate(savedInstanceState)
|
||||
Scoop.getInstance().apply(this)
|
||||
setContentView(R.layout.activity_add_source)
|
||||
val toolbar: Toolbar = findViewById(R.id.toolbar)
|
||||
|
||||
setSupportActionBar(toolbar)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
supportActionBar?.setDisplayShowHomeEnabled(true)
|
||||
|
||||
val mProgress: ProgressBar = findViewById(R.id.progress)
|
||||
val mForm: ConstraintLayout = findViewById(R.id.formContainer)
|
||||
val mNameInput: EditText = findViewById(R.id.nameInput)
|
||||
val mSourceUri: EditText = findViewById(R.id.sourceUri)
|
||||
val mTags: EditText = findViewById(R.id.tags)
|
||||
val mSpoutsSpinner: Spinner = findViewById(R.id.spoutsSpinner)
|
||||
val mSaveBtn: Button = findViewById(R.id.saveBtn)
|
||||
var api: SelfossApi? = null
|
||||
|
||||
try {
|
||||
api = SelfossApi(this, this@AddSourceActivity)
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
api = SelfossApi(
|
||||
this,
|
||||
this@AddSourceActivity,
|
||||
prefs.getBoolean("isSelfSignedCert", false),
|
||||
prefs.getBoolean("should_log_everything", false)
|
||||
)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
mustLoginToAddSource()
|
||||
}
|
||||
|
||||
val intent = intent
|
||||
if (Intent.ACTION_SEND == intent.action && "text/plain" == intent.type) {
|
||||
mSourceUri.setText(intent.getStringExtra(Intent.EXTRA_TEXT))
|
||||
mNameInput.setText(intent.getStringExtra(Intent.EXTRA_TITLE))
|
||||
maybeGetDetailsFromIntentSharing(intent, sourceUri, nameInput)
|
||||
|
||||
saveBtn.setOnClickListener {
|
||||
handleSaveSource(tags, nameInput.text.toString(), sourceUri.text.toString(), api!!)
|
||||
}
|
||||
|
||||
mSaveBtn.setOnClickListener {
|
||||
handleSaveSource(mTags, mNameInput.text.toString(), mSourceUri.text.toString(), api!!)
|
||||
val config = Config(this)
|
||||
|
||||
if (config.baseUrl.isEmpty() || !config.baseUrl.isBaseUrlValid()) {
|
||||
mustLoginToAddSource()
|
||||
} else {
|
||||
handleSpoutsSpinner(spoutsSpinner, api, progress, formContainer)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun handleSpoutsSpinner(spoutsSpinner: Spinner, api: SelfossApi?, mProgress: ProgressBar, formContainer: ConstraintLayout) {
|
||||
val spoutsKV = HashMap<String, String>()
|
||||
mSpoutsSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||
spoutsSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||
override fun onItemSelected(adapterView: AdapterView<*>, view: View, i: Int, l: Long) {
|
||||
val spoutName = (view as TextView).text.toString()
|
||||
mSpoutsValue = spoutsKV[spoutName]
|
||||
@ -71,48 +80,48 @@ class AddSourceActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
val config = Config(this)
|
||||
var items: Map<String, Spout>
|
||||
api!!.spouts().enqueue(object : Callback<Map<String, Spout>> {
|
||||
override fun onResponse(call: Call<Map<String, Spout>>, response: Response<Map<String, Spout>>) {
|
||||
if (response.body() != null) {
|
||||
items = response.body()!!
|
||||
|
||||
if (config.baseUrl.isEmpty() || !config.baseUrl.isUrlValid()) {
|
||||
mustLoginToAddSource()
|
||||
} else {
|
||||
|
||||
var items: Map<String, Spout>
|
||||
api!!.spouts().enqueue(object : Callback<Map<String, Spout>> {
|
||||
override fun onResponse(call: Call<Map<String, Spout>>, response: Response<Map<String, Spout>>) {
|
||||
if (response.body() != null) {
|
||||
items = response.body()!!
|
||||
|
||||
val itemsStrings = items.map { it.value.name }
|
||||
for ((key, value) in items) {
|
||||
spoutsKV.put(value.name, key)
|
||||
}
|
||||
|
||||
mProgress.visibility = View.GONE
|
||||
mForm.visibility = View.VISIBLE
|
||||
|
||||
val spinnerArrayAdapter =
|
||||
ArrayAdapter(
|
||||
this@AddSourceActivity,
|
||||
android.R.layout.simple_spinner_item,
|
||||
itemsStrings)
|
||||
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
mSpoutsSpinner.adapter = spinnerArrayAdapter
|
||||
|
||||
} else {
|
||||
handleProblemWithSpouts()
|
||||
val itemsStrings = items.map { it.value.name }
|
||||
for ((key, value) in items) {
|
||||
spoutsKV.put(value.name, key)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<Map<String, Spout>>, t: Throwable) {
|
||||
mProgress.visibility = View.GONE
|
||||
formContainer.visibility = View.VISIBLE
|
||||
|
||||
val spinnerArrayAdapter =
|
||||
ArrayAdapter(
|
||||
this@AddSourceActivity,
|
||||
android.R.layout.simple_spinner_item,
|
||||
itemsStrings)
|
||||
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
spoutsSpinner.adapter = spinnerArrayAdapter
|
||||
|
||||
} else {
|
||||
handleProblemWithSpouts()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleProblemWithSpouts() {
|
||||
Toast.makeText(this@AddSourceActivity, R.string.cant_get_spouts, Toast.LENGTH_SHORT).show()
|
||||
mProgress.visibility = View.GONE
|
||||
}
|
||||
})
|
||||
override fun onFailure(call: Call<Map<String, Spout>>, t: Throwable) {
|
||||
handleProblemWithSpouts()
|
||||
}
|
||||
|
||||
private fun handleProblemWithSpouts() {
|
||||
Toast.makeText(this@AddSourceActivity, R.string.cant_get_spouts, Toast.LENGTH_SHORT).show()
|
||||
mProgress.visibility = View.GONE
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun maybeGetDetailsFromIntentSharing(intent: Intent, sourceUri: EditText, nameInput: EditText) {
|
||||
if (Intent.ACTION_SEND == intent.action && "text/plain" == intent.type) {
|
||||
sourceUri.setText(intent.getStringExtra(Intent.EXTRA_TEXT))
|
||||
nameInput.setText(intent.getStringExtra(Intent.EXTRA_TITLE))
|
||||
}
|
||||
}
|
||||
|
||||
@ -123,17 +132,19 @@ class AddSourceActivity : AppCompatActivity() {
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun handleSaveSource(mTags: EditText, title: String, url: String, api: SelfossApi) {
|
||||
private fun handleSaveSource(tags: EditText, title: String, url: String, api: SelfossApi) {
|
||||
|
||||
if (title.isEmpty() || url.isEmpty() || mSpoutsValue == null || mSpoutsValue!!.isEmpty()) {
|
||||
val sourceDetailsAvailable = title.isEmpty() || url.isEmpty() || mSpoutsValue == null || mSpoutsValue!!.isEmpty()
|
||||
|
||||
if (sourceDetailsAvailable) {
|
||||
Toast.makeText(this, R.string.form_not_complete, Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
api.createSource(
|
||||
title,
|
||||
url,
|
||||
mSpoutsValue!!,
|
||||
mTags.text.toString(),
|
||||
""
|
||||
title,
|
||||
url,
|
||||
mSpoutsValue!!,
|
||||
tags.text.toString(),
|
||||
""
|
||||
).enqueue(object : Callback<SuccessResponse> {
|
||||
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {
|
||||
if (response.body() != null && response.body()!!.isSuccess) {
|
||||
|
@ -1,15 +1,14 @@
|
||||
package apps.amine.bou.readerforselfoss
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.preference.PreferenceManager
|
||||
import android.view.View
|
||||
|
||||
import agency.tango.materialintroscreen.MaterialIntroActivity
|
||||
import agency.tango.materialintroscreen.MessageButtonBehaviour
|
||||
import agency.tango.materialintroscreen.SlideFragmentBuilder
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.preference.PreferenceManager
|
||||
import android.support.v7.app.AppCompatDelegate
|
||||
import android.view.View
|
||||
|
||||
|
||||
class IntroActivity : MaterialIntroActivity() {
|
||||
@ -17,10 +16,12 @@ 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.mipmap.ic_launcher)
|
||||
.image(R.drawable.web_hi_res_512)
|
||||
.title(getString(R.string.intro_hello_title))
|
||||
.description(getString(R.string.intro_hello_message))
|
||||
.build())
|
||||
@ -28,7 +29,7 @@ class IntroActivity : MaterialIntroActivity() {
|
||||
addSlide(SlideFragmentBuilder()
|
||||
.backgroundColor(R.color.colorAccent)
|
||||
.buttonsColor(R.color.colorPrimary)
|
||||
.image(R.drawable.ic_info_outline_white_48dp)
|
||||
.image(R.drawable.ic_info_outline_white_48px)
|
||||
.title(getString(R.string.intro_needs_selfoss_title))
|
||||
.description(getString(R.string.intro_needs_selfoss_message))
|
||||
.build(),
|
||||
@ -40,7 +41,7 @@ class IntroActivity : MaterialIntroActivity() {
|
||||
addSlide(SlideFragmentBuilder()
|
||||
.backgroundColor(R.color.colorPrimaryDark)
|
||||
.buttonsColor(R.color.colorAccentDark)
|
||||
.image(R.drawable.ic_thumb_up_white_48dp)
|
||||
.image(R.drawable.ic_thumb_up_white_48px)
|
||||
.title(getString(R.string.intro_all_set_title))
|
||||
.description(getString(R.string.intro_all_set_message))
|
||||
.build())
|
||||
|
@ -6,7 +6,6 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import android.support.design.widget.TextInputLayout
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.support.v7.widget.Toolbar
|
||||
@ -15,41 +14,36 @@ import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.Button
|
||||
import android.widget.EditText
|
||||
import android.widget.Switch
|
||||
import android.widget.TextView
|
||||
|
||||
import android.widget.Toast
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
|
||||
import apps.amine.bou.readerforselfoss.utils.Config
|
||||
import apps.amine.bou.readerforselfoss.utils.isBaseUrlValid
|
||||
import com.crashlytics.android.Crashlytics
|
||||
import com.ftinc.scoop.Scoop
|
||||
import com.google.firebase.analytics.FirebaseAnalytics
|
||||
import com.mikepenz.aboutlibraries.Libs
|
||||
import com.mikepenz.aboutlibraries.LibsBuilder
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
import kotlinx.android.synthetic.main.activity_login.*
|
||||
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
|
||||
import apps.amine.bou.readerforselfoss.utils.Config
|
||||
import apps.amine.bou.readerforselfoss.utils.checkAndDisplayStoreApk
|
||||
import apps.amine.bou.readerforselfoss.utils.isUrlValid
|
||||
import com.ftinc.scoop.Scoop
|
||||
|
||||
|
||||
class LoginActivity : AppCompatActivity() {
|
||||
|
||||
private var inValidCount: Int = 0
|
||||
private var isWithSelfSignedCert = false
|
||||
private var isWithLogin = false
|
||||
private var isWithHTTPLogin = false
|
||||
|
||||
private lateinit var settings: SharedPreferences
|
||||
private lateinit var mFirebaseAnalytics: FirebaseAnalytics
|
||||
private lateinit var mUrlView: EditText
|
||||
private lateinit var mLoginView: TextView
|
||||
private lateinit var mHTTPLoginView: TextView
|
||||
private lateinit var mProgressView: View
|
||||
private lateinit var mPasswordView: EditText
|
||||
private lateinit var mHTTPPasswordView: EditText
|
||||
private lateinit var mLoginFormView: View
|
||||
private lateinit var editor: SharedPreferences.Editor
|
||||
private lateinit var firebaseAnalytics: FirebaseAnalytics
|
||||
private lateinit var userIdentifier: String
|
||||
private var logErrors: Boolean = false
|
||||
|
||||
|
||||
|
||||
@ -58,69 +52,72 @@ class LoginActivity : AppCompatActivity() {
|
||||
Scoop.getInstance().apply(this)
|
||||
setContentView(R.layout.activity_login)
|
||||
|
||||
val toolbar: Toolbar = findViewById(R.id.toolbar)
|
||||
setSupportActionBar(toolbar)
|
||||
|
||||
if (intent.getBooleanExtra("baseUrlFail", false)) {
|
||||
val alertDialog = AlertDialog.Builder(this).create()
|
||||
alertDialog.setTitle(getString(R.string.warning_wrong_url))
|
||||
alertDialog.setMessage(getString(R.string.base_url_error))
|
||||
alertDialog.setButton(
|
||||
AlertDialog.BUTTON_NEUTRAL,
|
||||
"OK",
|
||||
{ dialog, _ -> dialog.dismiss() })
|
||||
alertDialog.show()
|
||||
}
|
||||
handleBaseUrlFail()
|
||||
|
||||
|
||||
settings = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
|
||||
userIdentifier = settings.getString("unique_id", "")
|
||||
logErrors = settings.getBoolean("login_debug", false)
|
||||
|
||||
editor = settings.edit()
|
||||
|
||||
if (settings.getString("url", "").isNotEmpty()) {
|
||||
goToMain()
|
||||
} else {
|
||||
this@LoginActivity.checkAndDisplayStoreApk()
|
||||
}
|
||||
|
||||
mFirebaseAnalytics = FirebaseAnalytics.getInstance(this)
|
||||
mUrlView = findViewById(R.id.url)
|
||||
mLoginView = findViewById(R.id.login)
|
||||
mHTTPLoginView = findViewById(R.id.httpLogin)
|
||||
mPasswordView = findViewById(R.id.password)
|
||||
mHTTPPasswordView = findViewById(R.id.httpPassword)
|
||||
mLoginFormView = findViewById(R.id.login_form)
|
||||
mProgressView = findViewById(R.id.login_progress)
|
||||
firebaseAnalytics = FirebaseAnalytics.getInstance(this)
|
||||
|
||||
val mSwitch: Switch = findViewById(R.id.withLogin)
|
||||
val mHTTPSwitch: Switch = findViewById(R.id.withHttpLogin)
|
||||
val mLoginLayout: TextInputLayout = findViewById(R.id.loginLayout)
|
||||
val mHTTPLoginLayout: TextInputLayout = findViewById(R.id.httpLoginInput)
|
||||
val mPasswordLayout: TextInputLayout = findViewById(R.id.passwordLayout)
|
||||
val mHTTPPasswordLayout: TextInputLayout = findViewById(R.id.httpPasswordInput)
|
||||
val mEmailSignInButton: Button = findViewById(R.id.email_sign_in_button)
|
||||
handleActions()
|
||||
}
|
||||
|
||||
mPasswordView.setOnEditorActionListener(TextView.OnEditorActionListener { _, id, _ ->
|
||||
if (id == R.id.login || id == EditorInfo.IME_NULL) {
|
||||
private fun handleActions() {
|
||||
|
||||
withSelfhostedCert.setOnCheckedChangeListener { _, b ->
|
||||
isWithSelfSignedCert = !isWithSelfSignedCert
|
||||
val visi: Int = if (b) View.VISIBLE else View.GONE
|
||||
|
||||
warningText.visibility = visi
|
||||
}
|
||||
|
||||
passwordView.setOnEditorActionListener(TextView.OnEditorActionListener { _, id, _ ->
|
||||
if (id == R.id.loginView || id == EditorInfo.IME_NULL) {
|
||||
attemptLogin()
|
||||
return@OnEditorActionListener true
|
||||
}
|
||||
false
|
||||
})
|
||||
|
||||
mEmailSignInButton.setOnClickListener { attemptLogin() }
|
||||
signInButton.setOnClickListener { attemptLogin() }
|
||||
|
||||
mSwitch.setOnCheckedChangeListener { _, b ->
|
||||
withLogin.setOnCheckedChangeListener { _, b ->
|
||||
isWithLogin = !isWithLogin
|
||||
val visi: Int = if (b) View.VISIBLE else View.GONE
|
||||
|
||||
mLoginLayout.visibility = visi
|
||||
mPasswordLayout.visibility = visi
|
||||
loginLayout.visibility = visi
|
||||
passwordLayout.visibility = visi
|
||||
}
|
||||
|
||||
mHTTPSwitch.setOnCheckedChangeListener { _, b ->
|
||||
withHttpLogin.setOnCheckedChangeListener { _, b ->
|
||||
isWithHTTPLogin = !isWithHTTPLogin
|
||||
val visi: Int = if (b) View.VISIBLE else View.GONE
|
||||
|
||||
mHTTPLoginLayout.visibility = visi
|
||||
mHTTPPasswordLayout.visibility = visi
|
||||
httpLoginInput.visibility = visi
|
||||
httpPasswordInput.visibility = visi
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleBaseUrlFail() {
|
||||
if (intent.getBooleanExtra("baseUrlFail", false)) {
|
||||
val alertDialog = AlertDialog.Builder(this).create()
|
||||
alertDialog.setTitle(getString(R.string.warning_wrong_url))
|
||||
alertDialog.setMessage(getString(R.string.base_url_error))
|
||||
alertDialog.setButton(
|
||||
AlertDialog.BUTTON_NEUTRAL,
|
||||
"OK",
|
||||
{ dialog, _ -> dialog.dismiss() })
|
||||
alertDialog.show()
|
||||
}
|
||||
}
|
||||
|
||||
@ -133,25 +130,25 @@ class LoginActivity : AppCompatActivity() {
|
||||
private fun attemptLogin() {
|
||||
|
||||
// Reset errors.
|
||||
mUrlView.error = null
|
||||
mLoginView.error = null
|
||||
mHTTPLoginView.error = null
|
||||
mPasswordView.error = null
|
||||
mHTTPPasswordView.error = null
|
||||
urlView.error = null
|
||||
loginView.error = null
|
||||
httpLoginView.error = null
|
||||
passwordView.error = null
|
||||
httpPasswordView.error = null
|
||||
|
||||
// Store values at the time of the login attempt.
|
||||
val url = mUrlView.text.toString()
|
||||
val login = mLoginView.text.toString()
|
||||
val httpLogin = mHTTPLoginView.text.toString()
|
||||
val password = mPasswordView.text.toString()
|
||||
val httpPassword = mHTTPPasswordView.text.toString()
|
||||
val url = urlView.text.toString()
|
||||
val login = loginView.text.toString()
|
||||
val httpLogin = httpLoginView.text.toString()
|
||||
val password = passwordView.text.toString()
|
||||
val httpPassword = httpPasswordView.text.toString()
|
||||
|
||||
var cancel = false
|
||||
var focusView: View? = null
|
||||
|
||||
if (!url.isUrlValid()) {
|
||||
mUrlView.error = getString(R.string.login_url_problem)
|
||||
focusView = mUrlView
|
||||
if (!url.isBaseUrlValid()) {
|
||||
urlView.error = getString(R.string.login_url_problem)
|
||||
focusView = urlView
|
||||
cancel = true
|
||||
inValidCount++
|
||||
if (inValidCount == 3) {
|
||||
@ -159,9 +156,9 @@ class LoginActivity : AppCompatActivity() {
|
||||
alertDialog.setTitle(getString(R.string.warning_wrong_url))
|
||||
alertDialog.setMessage(getString(R.string.text_wrong_url))
|
||||
alertDialog.setButton(
|
||||
AlertDialog.BUTTON_NEUTRAL,
|
||||
"OK",
|
||||
{ dialog, _ -> dialog.dismiss() })
|
||||
AlertDialog.BUTTON_NEUTRAL,
|
||||
"OK",
|
||||
{ dialog, _ -> dialog.dismiss() })
|
||||
alertDialog.show()
|
||||
inValidCount = 0
|
||||
}
|
||||
@ -169,14 +166,14 @@ class LoginActivity : AppCompatActivity() {
|
||||
|
||||
if (isWithLogin || isWithHTTPLogin) {
|
||||
if (TextUtils.isEmpty(password)) {
|
||||
mPasswordView.error = getString(R.string.error_invalid_password)
|
||||
focusView = mPasswordView
|
||||
passwordView.error = getString(R.string.error_invalid_password)
|
||||
focusView = passwordView
|
||||
cancel = true
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(login)) {
|
||||
mLoginView.error = getString(R.string.error_field_required)
|
||||
focusView = mLoginView
|
||||
loginView.error = getString(R.string.error_field_required)
|
||||
focusView = loginView
|
||||
cancel = true
|
||||
}
|
||||
}
|
||||
@ -186,80 +183,85 @@ class LoginActivity : AppCompatActivity() {
|
||||
} else {
|
||||
showProgress(true)
|
||||
|
||||
val editor = settings.edit()
|
||||
editor.putString("url", url)
|
||||
editor.putString("login", login)
|
||||
editor.putString("httpUserName", httpLogin)
|
||||
editor.putString("password", password)
|
||||
editor.putString("httpPassword", httpPassword)
|
||||
editor.putBoolean("isSelfSignedCert", isWithSelfSignedCert)
|
||||
editor.apply()
|
||||
|
||||
val api = SelfossApi(this, this@LoginActivity)
|
||||
val api = SelfossApi(this, this@LoginActivity, isWithSelfSignedCert, isWithSelfSignedCert)
|
||||
api.login().enqueue(object : Callback<SuccessResponse> {
|
||||
private fun preferenceError() {
|
||||
private fun preferenceError(t: Throwable) {
|
||||
editor.remove("url")
|
||||
editor.remove("login")
|
||||
editor.remove("httpUserName")
|
||||
editor.remove("password")
|
||||
editor.remove("httpPassword")
|
||||
editor.apply()
|
||||
mUrlView.error = getString(R.string.wrong_infos)
|
||||
mLoginView.error = getString(R.string.wrong_infos)
|
||||
mPasswordView.error = getString(R.string.wrong_infos)
|
||||
mHTTPLoginView.error = getString(R.string.wrong_infos)
|
||||
mHTTPPasswordView.error = getString(R.string.wrong_infos)
|
||||
urlView.error = getString(R.string.wrong_infos)
|
||||
loginView.error = getString(R.string.wrong_infos)
|
||||
passwordView.error = getString(R.string.wrong_infos)
|
||||
httpLoginView.error = getString(R.string.wrong_infos)
|
||||
httpPasswordView.error = getString(R.string.wrong_infos)
|
||||
if (logErrors) {
|
||||
Crashlytics.setUserIdentifier(userIdentifier)
|
||||
Crashlytics.log(100, "LOGIN_DEBUG_ERRROR", t.message)
|
||||
Crashlytics.logException(t)
|
||||
Toast.makeText(this@LoginActivity, t.message, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
showProgress(false)
|
||||
}
|
||||
|
||||
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {
|
||||
if (response.body() != null && response.body()!!.isSuccess) {
|
||||
mFirebaseAnalytics.logEvent(FirebaseAnalytics.Event.LOGIN, Bundle())
|
||||
firebaseAnalytics.logEvent(FirebaseAnalytics.Event.LOGIN, Bundle())
|
||||
goToMain()
|
||||
} else {
|
||||
preferenceError()
|
||||
preferenceError(Exception("No response body..."))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
|
||||
preferenceError()
|
||||
preferenceError(t)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the progress UI and hides the login form.
|
||||
*/
|
||||
|
||||
private fun showProgress(show: Boolean) {
|
||||
val shortAnimTime = resources.getInteger(android.R.integer.config_shortAnimTime)
|
||||
|
||||
mLoginFormView.visibility = if (show) View.GONE else View.VISIBLE
|
||||
mLoginFormView
|
||||
.animate()
|
||||
.setDuration(shortAnimTime.toLong())
|
||||
.alpha(
|
||||
if (show) 0F else 1F
|
||||
).setListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
mLoginFormView.visibility = if (show) View.GONE else View.VISIBLE
|
||||
}
|
||||
})
|
||||
loginForm.visibility = if (show) View.GONE else View.VISIBLE
|
||||
loginForm
|
||||
.animate()
|
||||
.setDuration(shortAnimTime.toLong())
|
||||
.alpha(
|
||||
if (show) 0F else 1F
|
||||
).setListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
loginForm.visibility = if (show) View.GONE else View.VISIBLE
|
||||
}
|
||||
})
|
||||
|
||||
mProgressView.visibility = if (show) View.VISIBLE else View.GONE
|
||||
mProgressView
|
||||
.animate()
|
||||
.setDuration(shortAnimTime.toLong())
|
||||
.alpha(
|
||||
if (show) 1F else 0F
|
||||
).setListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
mProgressView.visibility = if (show) View.VISIBLE else View.GONE
|
||||
}
|
||||
})
|
||||
loginProgress.visibility = if (show) View.VISIBLE else View.GONE
|
||||
loginProgress
|
||||
.animate()
|
||||
.setDuration(shortAnimTime.toLong())
|
||||
.alpha(
|
||||
if (show) 1F else 0F
|
||||
).setListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
loginProgress.visibility = if (show) View.VISIBLE else View.GONE
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.login_menu, menu)
|
||||
menu.findItem(R.id.login_debug).isChecked = logErrors
|
||||
return true
|
||||
}
|
||||
|
||||
@ -267,10 +269,18 @@ class LoginActivity : AppCompatActivity() {
|
||||
when (item.itemId) {
|
||||
R.id.about -> {
|
||||
LibsBuilder()
|
||||
.withActivityStyle(Libs.ActivityStyle.LIGHT_DARK_TOOLBAR)
|
||||
.withAboutIconShown(true)
|
||||
.withAboutVersionShown(true)
|
||||
.start(this)
|
||||
.withActivityStyle(Libs.ActivityStyle.LIGHT_DARK_TOOLBAR)
|
||||
.withAboutIconShown(true)
|
||||
.withAboutVersionShown(true)
|
||||
.start(this)
|
||||
return true
|
||||
}
|
||||
R.id.login_debug -> {
|
||||
val newState = !item.isChecked
|
||||
item.isChecked = newState
|
||||
logErrors = newState
|
||||
editor.putBoolean("login_debug", newState)
|
||||
editor.apply()
|
||||
return true
|
||||
}
|
||||
else -> return super.onOptionsItemSelected(item)
|
||||
|
@ -6,47 +6,85 @@ import android.net.Uri
|
||||
import android.preference.PreferenceManager
|
||||
import android.support.multidex.MultiDexApplication
|
||||
import android.widget.ImageView
|
||||
import apps.amine.bou.readerforselfoss.utils.Config
|
||||
import com.anupcowkur.reservoir.Reservoir
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import com.crashlytics.android.Crashlytics
|
||||
import com.ftinc.scoop.Scoop
|
||||
import com.github.stkent.amplify.feedback.DefaultEmailFeedbackCollector
|
||||
import com.github.stkent.amplify.feedback.GooglePlayStoreFeedbackCollector
|
||||
import com.github.stkent.amplify.tracking.Amplify
|
||||
import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader
|
||||
import com.mikepenz.materialdrawer.util.DrawerImageLoader
|
||||
import io.fabric.sdk.android.Fabric
|
||||
import java.io.IOException
|
||||
import java.util.UUID.randomUUID
|
||||
|
||||
|
||||
|
||||
|
||||
class MyApp : MultiDexApplication() {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
if (!BuildConfig.DEBUG)
|
||||
Fabric.with(this, Crashlytics())
|
||||
Fabric.with(this, Crashlytics())
|
||||
|
||||
initAmplify()
|
||||
|
||||
initCache()
|
||||
|
||||
val prefs = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
|
||||
if (prefs.getString("unique_id", "").isEmpty()) {
|
||||
val editor = prefs.edit()
|
||||
editor.putString("unique_id", randomUUID().toString())
|
||||
editor.apply()
|
||||
}
|
||||
|
||||
initDrawerImageLoader()
|
||||
|
||||
initTheme()
|
||||
|
||||
tryToHandleBug()
|
||||
|
||||
}
|
||||
|
||||
private fun initAmplify() {
|
||||
Amplify.initSharedInstance(this)
|
||||
.setFeedbackEmailAddress(getString(R.string.feedback_email))
|
||||
.setAlwaysShow(BuildConfig.DEBUG)
|
||||
.applyAllDefaultRules()
|
||||
.setPositiveFeedbackCollectors(GooglePlayStoreFeedbackCollector())
|
||||
.setCriticalFeedbackCollectors(DefaultEmailFeedbackCollector(BuildConfig.FEEDBACK_EMAIL))
|
||||
.applyAllDefaultRules()
|
||||
}
|
||||
|
||||
private fun initCache() {
|
||||
try {
|
||||
Reservoir.init(this, 8192) //in bytes
|
||||
} catch (e: IOException) {
|
||||
//failure
|
||||
}
|
||||
}
|
||||
|
||||
private fun initDrawerImageLoader() {
|
||||
DrawerImageLoader.init(object : AbstractDrawerImageLoader() {
|
||||
override fun set(imageView: ImageView?, uri: Uri?, placeholder: Drawable?, tag: String?) {
|
||||
Glide.with(imageView?.context).load(uri).placeholder(placeholder).into(imageView)
|
||||
Glide.with(imageView?.context)
|
||||
.load(uri)
|
||||
.apply(RequestOptions.fitCenterTransform()
|
||||
.placeholder(placeholder))
|
||||
.into(imageView)
|
||||
}
|
||||
|
||||
override fun cancel(imageView: ImageView?) {
|
||||
Glide.clear(imageView)
|
||||
Glide.with(imageView?.context).clear(imageView)
|
||||
}
|
||||
|
||||
override fun placeholder(ctx: Context?, tag: String?): Drawable {
|
||||
return baseContext.resources.getDrawable(R.mipmap.ic_launcher)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun initTheme() {
|
||||
Scoop.waffleCone()
|
||||
.addFlavor(getString(R.string.default_theme), R.style.NoBar, true)
|
||||
.addFlavor(getString(R.string.default_dark_theme), R.style.NoBarDark)
|
||||
@ -64,6 +102,17 @@ class MyApp : MultiDexApplication() {
|
||||
.addFlavor(getString(R.string.red_teal_dark_theme), R.style.NoBarRedTealDark)
|
||||
.setSharedPreferences(PreferenceManager.getDefaultSharedPreferences(this))
|
||||
.initialize()
|
||||
}
|
||||
|
||||
private fun tryToHandleBug() {
|
||||
val oldHandler = Thread.getDefaultUncaughtExceptionHandler()
|
||||
|
||||
Thread.setDefaultUncaughtExceptionHandler { thread, e ->
|
||||
if (e is java.lang.NoClassDefFoundError && e.stackTrace.asList().any { it.toString().contains("android.view.ViewDebug") })
|
||||
Unit
|
||||
else
|
||||
oldHandler.uncaughtException(thread, e)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,111 +1,190 @@
|
||||
package apps.amine.bou.readerforselfoss
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.preference.PreferenceManager
|
||||
import android.support.customtabs.CustomTabsIntent
|
||||
import android.support.design.widget.FloatingActionButton
|
||||
import android.support.v4.widget.NestedScrollView
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.text.Html
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageButton
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
|
||||
import com.bumptech.glide.Glide
|
||||
import org.sufficientlysecure.htmltextview.HtmlHttpImageGetter
|
||||
import org.sufficientlysecure.htmltextview.HtmlTextView
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
import xyz.klinker.android.drag_dismiss.activity.DragDismissActivity
|
||||
|
||||
import apps.amine.bou.readerforselfoss.api.mercury.MercuryApi
|
||||
import apps.amine.bou.readerforselfoss.api.mercury.ParsedContent
|
||||
import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent
|
||||
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
|
||||
import apps.amine.bou.readerforselfoss.utils.isEmptyOrNullOrNullString
|
||||
import apps.amine.bou.readerforselfoss.utils.openItemUrl
|
||||
import apps.amine.bou.readerforselfoss.utils.shareLink
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import com.crashlytics.android.Crashlytics
|
||||
import com.ftinc.scoop.Scoop
|
||||
import com.github.rubensousa.floatingtoolbar.FloatingToolbar
|
||||
import org.sufficientlysecure.htmltextview.HtmlHttpImageGetter
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
import kotlinx.android.synthetic.main.activity_reader.*
|
||||
|
||||
|
||||
class ReaderActivity : DragDismissActivity() {
|
||||
class ReaderActivity : AppCompatActivity() {
|
||||
private lateinit var mCustomTabActivityHelper: CustomTabActivityHelper
|
||||
//private lateinit var content: HtmlTextView
|
||||
private lateinit var url: String
|
||||
private lateinit var contentText: String
|
||||
private lateinit var contentSource: String
|
||||
private lateinit var contentImage: String
|
||||
private lateinit var contentTitle: String
|
||||
private lateinit var fab: FloatingActionButton
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
mCustomTabActivityHelper.bindCustomTabsService(this)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
mCustomTabActivityHelper.unbindCustomTabsService(this)
|
||||
}
|
||||
|
||||
override fun onCreateContent(inflater: LayoutInflater, parent: ViewGroup, savedInstanceState: Bundle?): View {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
Scoop.getInstance().apply(this)
|
||||
val v = inflater.inflate(R.layout.activity_reader, parent, false)
|
||||
showProgressBar()
|
||||
setContentView(R.layout.activity_reader)
|
||||
|
||||
val image: ImageView = v.findViewById(R.id.imageView)
|
||||
val source: TextView = v.findViewById(R.id.source)
|
||||
val title: TextView = v.findViewById(R.id.title)
|
||||
val content: HtmlTextView = v.findViewById(R.id.content)
|
||||
val url = intent.getStringExtra("url")
|
||||
val parser = MercuryApi(getString(R.string.mercury))
|
||||
val browserBtn: ImageButton = v.findViewById(R.id.browserBtn)
|
||||
val shareBtn: ImageButton = v.findViewById(R.id.shareBtn)
|
||||
url = intent.getStringExtra("url")
|
||||
contentText = intent.getStringExtra("content")
|
||||
contentTitle = intent.getStringExtra("title")
|
||||
contentImage = intent.getStringExtra("image")
|
||||
contentSource = intent.getStringExtra("source")
|
||||
|
||||
fab = findViewById(R.id.fab)
|
||||
val mFloatingToolbar: FloatingToolbar = findViewById(R.id.floatingToolbar)
|
||||
mFloatingToolbar.attachFab(fab)
|
||||
|
||||
val customTabsIntent = this@ReaderActivity.buildCustomTabsIntent()
|
||||
mCustomTabActivityHelper = CustomTabActivityHelper()
|
||||
mCustomTabActivityHelper.bindCustomTabsService(this)
|
||||
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
|
||||
mFloatingToolbar.setClickListener(object : FloatingToolbar.ItemClickListener {
|
||||
override fun onItemClick(item: MenuItem) {
|
||||
when (item.itemId) {
|
||||
R.id.more_action -> getContentFromMercury(customTabsIntent, prefs)
|
||||
R.id.share_action -> this@ReaderActivity.shareLink(url)
|
||||
R.id.open_action -> this@ReaderActivity.openItemUrl(
|
||||
url,
|
||||
contentText,
|
||||
contentImage,
|
||||
contentTitle,
|
||||
contentSource,
|
||||
customTabsIntent,
|
||||
false,
|
||||
false,
|
||||
this@ReaderActivity)
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
override fun onItemLongClick(item: MenuItem?) {
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
if (contentText.isEmptyOrNullOrNullString()) {
|
||||
getContentFromMercury(customTabsIntent, prefs)
|
||||
} else {
|
||||
source.text = contentSource
|
||||
titleView.text = contentTitle
|
||||
tryToHandleHtml(contentText, customTabsIntent, prefs)
|
||||
|
||||
if (!contentImage.isEmptyOrNullOrNullString()) {
|
||||
imageView.visibility = View.VISIBLE
|
||||
Glide
|
||||
.with(baseContext)
|
||||
.asBitmap()
|
||||
.load(contentImage)
|
||||
.apply(RequestOptions.fitCenterTransform())
|
||||
.into(imageView)
|
||||
} else {
|
||||
imageView.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
nestedScrollView.setOnScrollChangeListener(NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
|
||||
if (scrollY > oldScrollY) {
|
||||
fab.hide()
|
||||
} else {
|
||||
if (mFloatingToolbar.isShowing) mFloatingToolbar.hide() else fab.show()
|
||||
}
|
||||
})
|
||||
|
||||
content.movementMethod = LinkMovementMethod.getInstance()
|
||||
}
|
||||
|
||||
private fun getContentFromMercury(customTabsIntent: CustomTabsIntent, prefs: SharedPreferences) {
|
||||
progressBar.visibility = View.VISIBLE
|
||||
val parser = MercuryApi(BuildConfig.MERCURY_KEY, prefs.getBoolean("should_log_everything", false))
|
||||
|
||||
parser.parseUrl(url).enqueue(object : Callback<ParsedContent> {
|
||||
override fun onResponse(call: Call<ParsedContent>, response: Response<ParsedContent>) {
|
||||
if (response.body() != null && response.body()!!.content != null && response.body()!!.content.isNotEmpty()) {
|
||||
source.text = response.body()!!.domain
|
||||
title.text = response.body()!!.title
|
||||
titleView.text = response.body()!!.title
|
||||
this@ReaderActivity.url = response.body()!!.url
|
||||
|
||||
if (response.body()!!.content != null && !response.body()!!.content.isEmpty()) {
|
||||
try {
|
||||
content.setHtml(response.body()!!.content, HtmlHttpImageGetter(content, null, true))
|
||||
} catch (e: IndexOutOfBoundsException) {
|
||||
openInBrowserAfterFailing()
|
||||
}
|
||||
tryToHandleHtml(response.body()!!.content, customTabsIntent, prefs)
|
||||
}
|
||||
if (response.body()!!.lead_image_url != null && !response.body()!!.lead_image_url.isEmpty())
|
||||
|
||||
if (response.body()!!.lead_image_url != null && !response.body()!!.lead_image_url.isEmpty()) {
|
||||
imageView.visibility = View.VISIBLE
|
||||
Glide
|
||||
.with(baseContext)
|
||||
.load(response.body()!!.lead_image_url)
|
||||
.asBitmap()
|
||||
.fitCenter()
|
||||
.into(image)
|
||||
|
||||
shareBtn.setOnClickListener {
|
||||
this@ReaderActivity.shareLink(response.body()!!.url)
|
||||
.with(baseContext)
|
||||
.asBitmap()
|
||||
.load(response.body()!!.lead_image_url)
|
||||
.apply(RequestOptions.fitCenterTransform())
|
||||
.into(imageView)
|
||||
} else {
|
||||
imageView.visibility = View.GONE
|
||||
}
|
||||
|
||||
browserBtn.setOnClickListener {
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
intent.data = Uri.parse(response.body()!!.url)
|
||||
startActivity(intent)
|
||||
}
|
||||
nestedScrollView.scrollTo(0, 0)
|
||||
|
||||
hideProgressBar()
|
||||
} else openInBrowserAfterFailing()
|
||||
progressBar.visibility = View.GONE
|
||||
} else openInBrowserAfterFailing(customTabsIntent)
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<ParsedContent>, t: Throwable) = openInBrowserAfterFailing()
|
||||
|
||||
private fun openInBrowserAfterFailing() {
|
||||
CustomTabActivityHelper.openCustomTab(this@ReaderActivity, customTabsIntent, Uri.parse(url)
|
||||
) { _, uri ->
|
||||
val intent = Intent(Intent.ACTION_VIEW, uri)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
startActivity(intent)
|
||||
}
|
||||
finish()
|
||||
}
|
||||
override fun onFailure(call: Call<ParsedContent>, t: Throwable) = openInBrowserAfterFailing(customTabsIntent)
|
||||
})
|
||||
return v
|
||||
}
|
||||
|
||||
private fun tryToHandleHtml(c: String, customTabsIntent: CustomTabsIntent, prefs: SharedPreferences) {
|
||||
try {
|
||||
content.text = Html.fromHtml(c, HtmlHttpImageGetter(content, null, true), null)
|
||||
|
||||
//content.setHtml(response.body()!!.content, HtmlHttpImageGetter(content, null, true))
|
||||
} catch (e: Exception) {
|
||||
Crashlytics.setUserIdentifier(prefs.getString("unique_id", ""))
|
||||
Crashlytics.log(100, "CANT_TRANSFORM_TO_HTML", e.message)
|
||||
Crashlytics.logException(e)
|
||||
openInBrowserAfterFailing(customTabsIntent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun openInBrowserAfterFailing(customTabsIntent: CustomTabsIntent) {
|
||||
progressBar.visibility = View.GONE
|
||||
this@ReaderActivity.openItemUrl(
|
||||
url,
|
||||
contentText,
|
||||
contentImage,
|
||||
contentTitle,
|
||||
contentSource,
|
||||
customTabsIntent,
|
||||
true,
|
||||
false,
|
||||
this@ReaderActivity
|
||||
)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
@ -2,46 +2,48 @@ package apps.amine.bou.readerforselfoss
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.preference.PreferenceManager
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.support.v7.widget.LinearLayoutManager
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.support.v7.widget.Toolbar
|
||||
import android.widget.Toast
|
||||
|
||||
import com.melnykov.fab.FloatingActionButton
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
|
||||
import apps.amine.bou.readerforselfoss.adapters.SourcesListAdapter
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.Sources
|
||||
import com.ftinc.scoop.Scoop
|
||||
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
import kotlinx.android.synthetic.main.activity_sources.*
|
||||
|
||||
class SourcesActivity : AppCompatActivity() {
|
||||
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
Scoop.getInstance().apply(this)
|
||||
setContentView(R.layout.activity_sources)
|
||||
val toolbar: Toolbar = findViewById(R.id.toolbar)
|
||||
|
||||
setSupportActionBar(toolbar)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
supportActionBar?.setDisplayShowHomeEnabled(true)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
recyclerView.clearOnScrollListeners()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
val mFab: FloatingActionButton = findViewById(R.id.fab)
|
||||
val mRecyclerView: RecyclerView = findViewById(R.id.activity_sources)
|
||||
val mLayoutManager = LinearLayoutManager(this)
|
||||
val api = SelfossApi(this, this@SourcesActivity)
|
||||
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
|
||||
val api = SelfossApi(this, this@SourcesActivity, prefs.getBoolean("isSelfSignedCert", false), prefs.getBoolean("should_log_everything", false))
|
||||
var items: ArrayList<Sources> = ArrayList()
|
||||
|
||||
mFab.attachToRecyclerView(mRecyclerView)
|
||||
mRecyclerView.setHasFixedSize(true)
|
||||
mRecyclerView.layoutManager = mLayoutManager
|
||||
recyclerView.setHasFixedSize(true)
|
||||
recyclerView.layoutManager = mLayoutManager
|
||||
|
||||
api.sources.enqueue(object : Callback<List<Sources>> {
|
||||
override fun onResponse(call: Call<List<Sources>>, response: Response<List<Sources>>) {
|
||||
@ -49,7 +51,7 @@ class SourcesActivity : AppCompatActivity() {
|
||||
items = response.body() as ArrayList<Sources>
|
||||
}
|
||||
val mAdapter = SourcesListAdapter(this@SourcesActivity, items, api)
|
||||
mRecyclerView.adapter = mAdapter
|
||||
recyclerView.adapter = mAdapter
|
||||
mAdapter.notifyDataSetChanged()
|
||||
if (items.isEmpty()) Toast.makeText(this@SourcesActivity, R.string.nothing_here, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
@ -59,7 +61,7 @@ class SourcesActivity : AppCompatActivity() {
|
||||
}
|
||||
})
|
||||
|
||||
mFab.setOnClickListener {
|
||||
fab.setOnClickListener {
|
||||
startActivity(Intent(this@SourcesActivity, AddSourceActivity::class.java))
|
||||
}
|
||||
}
|
||||
|
@ -2,43 +2,41 @@ package apps.amine.bou.readerforselfoss.adapters
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.support.constraint.ConstraintLayout
|
||||
import android.support.design.widget.Snackbar
|
||||
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory
|
||||
import android.support.v7.widget.CardView
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.text.Html
|
||||
import android.text.format.DateUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageButton
|
||||
import android.widget.ImageView
|
||||
import android.widget.ImageView.ScaleType
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
|
||||
import com.amulyakhare.textdrawable.TextDrawable
|
||||
import com.amulyakhare.textdrawable.util.ColorGenerator
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.target.BitmapImageViewTarget
|
||||
import com.like.LikeButton
|
||||
import com.like.OnLikeListener
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
import java.text.ParseException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
import apps.amine.bou.readerforselfoss.R
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.Item
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
|
||||
import apps.amine.bou.readerforselfoss.themes.AppColors
|
||||
import apps.amine.bou.readerforselfoss.utils.*
|
||||
import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent
|
||||
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
|
||||
import apps.amine.bou.readerforselfoss.utils.glide.bitmapCenterCrop
|
||||
import apps.amine.bou.readerforselfoss.utils.glide.circularBitmapDrawable
|
||||
import apps.amine.bou.readerforselfoss.utils.openInBrowserAsNewTask
|
||||
import apps.amine.bou.readerforselfoss.utils.openItemUrl
|
||||
import apps.amine.bou.readerforselfoss.utils.shareLink
|
||||
import apps.amine.bou.readerforselfoss.utils.sourceAndDateText
|
||||
import apps.amine.bou.readerforselfoss.utils.succeeded
|
||||
import apps.amine.bou.readerforselfoss.utils.toTextDrawableString
|
||||
import com.amulyakhare.textdrawable.TextDrawable
|
||||
import com.amulyakhare.textdrawable.util.ColorGenerator
|
||||
import com.bumptech.glide.Glide
|
||||
import com.crashlytics.android.Crashlytics
|
||||
import com.like.LikeButton
|
||||
import com.like.OnLikeListener
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
import kotlinx.android.synthetic.main.card_item.view.*
|
||||
|
||||
class ItemCardAdapter(private val app: Activity,
|
||||
private val items: ArrayList<Item>,
|
||||
@ -47,12 +45,14 @@ class ItemCardAdapter(private val app: Activity,
|
||||
private val internalBrowser: Boolean,
|
||||
private val articleViewer: Boolean,
|
||||
private val fullHeightCards: Boolean,
|
||||
private val appColors: AppColors) : RecyclerView.Adapter<ItemCardAdapter.ViewHolder>() {
|
||||
private val appColors: AppColors,
|
||||
val debugReadingItems: Boolean,
|
||||
val userIdentifier: String) : RecyclerView.Adapter<ItemCardAdapter.ViewHolder>() {
|
||||
private val c: Context = app.baseContext
|
||||
private val generator: ColorGenerator = ColorGenerator.MATERIAL
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val v = LayoutInflater.from(c).inflate(R.layout.card_item, parent, false) as ConstraintLayout
|
||||
val v = LayoutInflater.from(c).inflate(R.layout.card_item, parent, false) as CardView
|
||||
return ViewHolder(v)
|
||||
}
|
||||
|
||||
@ -60,37 +60,32 @@ class ItemCardAdapter(private val app: Activity,
|
||||
val itm = items[position]
|
||||
|
||||
|
||||
holder.saveBtn.isLiked = itm.starred
|
||||
holder.title.text = Html.fromHtml(itm.title)
|
||||
holder.mView.favButton.isLiked = itm.starred
|
||||
holder.mView.title.text = Html.fromHtml(itm.title)
|
||||
|
||||
holder.sourceTitleAndDate.text = itm.sourceAndDateText()
|
||||
holder.mView.sourceTitleAndDate.text = itm.sourceAndDateText()
|
||||
|
||||
if (itm.getThumbnail(c).isEmpty()) {
|
||||
Glide.clear(holder.itemImage)
|
||||
holder.itemImage.setImageDrawable(null)
|
||||
Glide.with(c).clear(holder.mView.itemImage)
|
||||
holder.mView.itemImage.setImageDrawable(null)
|
||||
} else {
|
||||
if (fullHeightCards) {
|
||||
c.bitmapFitCenter(itm.getThumbnail(c), holder.itemImage)
|
||||
} else {
|
||||
c.bitmapCenterCrop(itm.getThumbnail(c), holder.itemImage)
|
||||
}
|
||||
c.bitmapCenterCrop(itm.getThumbnail(c), holder.mView.itemImage)
|
||||
}
|
||||
|
||||
val fHolder = holder
|
||||
if (itm.getIcon(c).isEmpty()) {
|
||||
val color = generator.getColor(itm.sourcetitle)
|
||||
|
||||
val drawable =
|
||||
TextDrawable
|
||||
.builder()
|
||||
.round()
|
||||
.build(itm.sourcetitle.toTextDrawableString(), color)
|
||||
holder.sourceImage.setImageDrawable(drawable)
|
||||
TextDrawable
|
||||
.builder()
|
||||
.round()
|
||||
.build(itm.sourcetitle.toTextDrawableString(), color)
|
||||
holder.mView.sourceImage.setImageDrawable(drawable)
|
||||
} else {
|
||||
c.circularBitmapDrawable(itm.getIcon(c), holder.sourceImage)
|
||||
c.circularBitmapDrawable(itm.getIcon(c), holder.mView.sourceImage)
|
||||
}
|
||||
|
||||
holder.saveBtn.isLiked = itm.starred
|
||||
holder.mView.favButton.isLiked = itm.starred
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
@ -130,11 +125,31 @@ class ItemCardAdapter(private val app: Activity,
|
||||
|
||||
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)
|
||||
@ -143,43 +158,28 @@ class ItemCardAdapter(private val app: Activity,
|
||||
|
||||
}
|
||||
|
||||
inner class ViewHolder(val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) {
|
||||
lateinit var saveBtn: LikeButton
|
||||
lateinit var browserBtn: ImageButton
|
||||
lateinit var shareBtn: ImageButton
|
||||
lateinit var itemImage: ImageView
|
||||
lateinit var sourceImage: ImageView
|
||||
lateinit var title: TextView
|
||||
lateinit var sourceTitleAndDate: TextView
|
||||
|
||||
inner class ViewHolder(val mView: CardView) : RecyclerView.ViewHolder(mView) {
|
||||
init {
|
||||
(mView.findViewById<CardView>(R.id.card)).setCardBackgroundColor(appColors.cardBackground)
|
||||
mView.setCardBackgroundColor(appColors.cardBackground)
|
||||
handleClickListeners()
|
||||
handleCustomTabActions()
|
||||
}
|
||||
|
||||
private fun handleClickListeners() {
|
||||
sourceImage = mView.findViewById(R.id.sourceImage)
|
||||
itemImage = mView.findViewById(R.id.itemImage)
|
||||
title = mView.findViewById(R.id.title)
|
||||
sourceTitleAndDate = mView.findViewById(R.id.sourceTitleAndDate)
|
||||
saveBtn = mView.findViewById(R.id.favButton)
|
||||
shareBtn = mView.findViewById(R.id.shareBtn)
|
||||
browserBtn = mView.findViewById(R.id.browserBtn)
|
||||
|
||||
if (!fullHeightCards) {
|
||||
itemImage.maxHeight = c.resources.getDimension(R.dimen.card_image_max_height).toInt()
|
||||
itemImage.scaleType = ScaleType.CENTER_CROP
|
||||
mView.itemImage.maxHeight = c.resources.getDimension(R.dimen.card_image_max_height).toInt()
|
||||
mView.itemImage.scaleType = ScaleType.CENTER_CROP
|
||||
}
|
||||
|
||||
saveBtn.setOnLikeListener(object : OnLikeListener {
|
||||
mView.favButton.setOnLikeListener(object : OnLikeListener {
|
||||
override fun liked(likeButton: LikeButton) {
|
||||
val (id) = items[adapterPosition]
|
||||
api.starrItem(id).enqueue(object : Callback<SuccessResponse> {
|
||||
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {}
|
||||
|
||||
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
|
||||
saveBtn.isLiked = false
|
||||
mView.favButton.isLiked = false
|
||||
Toast.makeText(c, R.string.cant_mark_favortie, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
})
|
||||
@ -191,19 +191,19 @@ class ItemCardAdapter(private val app: Activity,
|
||||
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {}
|
||||
|
||||
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
|
||||
saveBtn.isLiked = true
|
||||
mView.favButton.isLiked = true
|
||||
Toast.makeText(c, R.string.cant_unmark_favortie, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
shareBtn.setOnClickListener {
|
||||
mView.shareBtn.setOnClickListener {
|
||||
c.shareLink(items[adapterPosition].getLinkDecoded())
|
||||
}
|
||||
|
||||
browserBtn.setOnClickListener {
|
||||
c.openInBrowser(items[adapterPosition])
|
||||
mView.browserBtn.setOnClickListener {
|
||||
c.openInBrowserAsNewTask(items[adapterPosition])
|
||||
}
|
||||
}
|
||||
|
||||
@ -212,11 +212,15 @@ class ItemCardAdapter(private val app: Activity,
|
||||
helper.bindCustomTabsService(app)
|
||||
|
||||
mView.setOnClickListener {
|
||||
c.openItemUrl(items[adapterPosition],
|
||||
customTabsIntent,
|
||||
internalBrowser,
|
||||
articleViewer,
|
||||
app)
|
||||
c.openItemUrl(items[adapterPosition].getLinkDecoded(),
|
||||
items[adapterPosition].content,
|
||||
items[adapterPosition].getThumbnail(c),
|
||||
items[adapterPosition].title,
|
||||
items[adapterPosition].sourceAndDateText(),
|
||||
customTabsIntent,
|
||||
internalBrowser,
|
||||
articleViewer,
|
||||
app)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,43 +3,41 @@ package apps.amine.bou.readerforselfoss.adapters
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.net.Uri
|
||||
import android.support.constraint.ConstraintLayout
|
||||
import android.support.design.widget.Snackbar
|
||||
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.text.Html
|
||||
import android.text.format.DateUtils
|
||||
import android.util.TypedValue
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.*
|
||||
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import apps.amine.bou.readerforselfoss.R
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.Item
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
|
||||
import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent
|
||||
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
|
||||
import apps.amine.bou.readerforselfoss.utils.glide.bitmapCenterCrop
|
||||
import apps.amine.bou.readerforselfoss.utils.glide.circularBitmapDrawable
|
||||
import apps.amine.bou.readerforselfoss.utils.openInBrowserAsNewTask
|
||||
import apps.amine.bou.readerforselfoss.utils.openItemUrl
|
||||
import apps.amine.bou.readerforselfoss.utils.shareLink
|
||||
import apps.amine.bou.readerforselfoss.utils.sourceAndDateText
|
||||
import apps.amine.bou.readerforselfoss.utils.succeeded
|
||||
import com.amulyakhare.textdrawable.TextDrawable
|
||||
import com.amulyakhare.textdrawable.util.ColorGenerator
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.target.BitmapImageViewTarget
|
||||
import com.crashlytics.android.Crashlytics
|
||||
import com.like.LikeButton
|
||||
import com.like.OnLikeListener
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
import java.text.ParseException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
import apps.amine.bou.readerforselfoss.R
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.Item
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
|
||||
import apps.amine.bou.readerforselfoss.themes.AppColors
|
||||
import apps.amine.bou.readerforselfoss.utils.*
|
||||
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
|
||||
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlinx.android.synthetic.main.list_item.view.*
|
||||
|
||||
class ItemListAdapter(private val app: Activity,
|
||||
private val items: ArrayList<Item>,
|
||||
@ -47,7 +45,9 @@ class ItemListAdapter(private val app: Activity,
|
||||
private val helper: CustomTabActivityHelper,
|
||||
private val clickBehavior: Boolean,
|
||||
private val internalBrowser: Boolean,
|
||||
private val articleViewer: Boolean) : RecyclerView.Adapter<ItemListAdapter.ViewHolder>() {
|
||||
private val articleViewer: Boolean,
|
||||
val debugReadingItems: Boolean,
|
||||
val userIdentifier: String) : RecyclerView.Adapter<ItemListAdapter.ViewHolder>() {
|
||||
private val generator: ColorGenerator = ColorGenerator.MATERIAL
|
||||
private val c: Context = app.baseContext
|
||||
private val bars: ArrayList<Boolean> = ArrayList(Collections.nCopies(items.size + 1, false))
|
||||
@ -61,27 +61,27 @@ class ItemListAdapter(private val app: Activity,
|
||||
val itm = items[position]
|
||||
|
||||
|
||||
holder.saveBtn.isLiked = itm.starred
|
||||
holder.title.text = Html.fromHtml(itm.title)
|
||||
holder.mView.favButton.isLiked = itm.starred
|
||||
holder.mView.title.text = Html.fromHtml(itm.title)
|
||||
|
||||
holder.sourceTitleAndDate.text = itm.sourceAndDateText()
|
||||
holder.mView.sourceTitleAndDate.text = itm.sourceAndDateText()
|
||||
|
||||
if (itm.getThumbnail(c).isEmpty()) {
|
||||
val sizeInInt = 46
|
||||
val sizeInDp = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP, sizeInInt.toFloat(), c.resources
|
||||
.displayMetrics).toInt()
|
||||
TypedValue.COMPLEX_UNIT_DIP, sizeInInt.toFloat(), c.resources
|
||||
.displayMetrics).toInt()
|
||||
|
||||
val marginInInt = 16
|
||||
val marginInDp = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP, marginInInt.toFloat(), c.resources
|
||||
.displayMetrics).toInt()
|
||||
|
||||
val params = holder.sourceImage.layoutParams as ViewGroup.MarginLayoutParams
|
||||
val params = holder.mView.itemImage.layoutParams as ViewGroup.MarginLayoutParams
|
||||
params.height = sizeInDp
|
||||
params.width = sizeInDp
|
||||
params.setMargins(marginInDp, 0, 0, 0)
|
||||
holder.sourceImage.layoutParams = params
|
||||
holder.mView.itemImage.layoutParams = params
|
||||
|
||||
if (itm.getIcon(c).isEmpty()) {
|
||||
val color = generator.getColor(itm.sourcetitle)
|
||||
@ -93,17 +93,17 @@ class ItemListAdapter(private val app: Activity,
|
||||
val builder = TextDrawable.builder().round()
|
||||
|
||||
val drawable = builder.build(textDrawable.toString(), color)
|
||||
holder.sourceImage.setImageDrawable(drawable)
|
||||
holder.mView.itemImage.setImageDrawable(drawable)
|
||||
} else {
|
||||
c.circularBitmapDrawable(itm.getIcon(c), holder.sourceImage)
|
||||
c.circularBitmapDrawable(itm.getIcon(c), holder.mView.itemImage)
|
||||
}
|
||||
} else {
|
||||
c.bitmapCenterCrop(itm.getThumbnail(c), holder.sourceImage)
|
||||
c.bitmapCenterCrop(itm.getThumbnail(c), holder.mView.itemImage)
|
||||
}
|
||||
|
||||
if (bars[position]) holder.actionBar.visibility = View.VISIBLE else holder.actionBar.visibility = View.GONE
|
||||
if (bars[position]) holder.mView.actionBar.visibility = View.VISIBLE else holder.mView.actionBar.visibility = View.GONE
|
||||
|
||||
holder.saveBtn.isLiked = itm.starred
|
||||
holder.mView.favButton.isLiked = itm.starred
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = items.size
|
||||
@ -142,11 +142,31 @@ class ItemListAdapter(private val app: Activity,
|
||||
|
||||
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)
|
||||
@ -156,13 +176,6 @@ class ItemListAdapter(private val app: Activity,
|
||||
}
|
||||
|
||||
inner class ViewHolder(val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) {
|
||||
lateinit var saveBtn: LikeButton
|
||||
lateinit var browserBtn: ImageButton
|
||||
lateinit var shareBtn: ImageButton
|
||||
lateinit var actionBar: RelativeLayout
|
||||
lateinit var sourceImage: ImageView
|
||||
lateinit var title: TextView
|
||||
lateinit var sourceTitleAndDate: TextView
|
||||
|
||||
init {
|
||||
handleClickListeners()
|
||||
@ -170,23 +183,15 @@ class ItemListAdapter(private val app: Activity,
|
||||
}
|
||||
|
||||
private fun handleClickListeners() {
|
||||
actionBar = mView.findViewById(R.id.actionBar)
|
||||
sourceImage = mView.findViewById(R.id.itemImage)
|
||||
title = mView.findViewById(R.id.title)
|
||||
sourceTitleAndDate = mView.findViewById(R.id.sourceTitleAndDate)
|
||||
saveBtn = mView.findViewById(R.id.favButton)
|
||||
shareBtn = mView.findViewById(R.id.shareBtn)
|
||||
browserBtn = mView.findViewById(R.id.browserBtn)
|
||||
|
||||
|
||||
saveBtn.setOnLikeListener(object : OnLikeListener {
|
||||
mView.favButton.setOnLikeListener(object : OnLikeListener {
|
||||
override fun liked(likeButton: LikeButton) {
|
||||
val (id) = items[adapterPosition]
|
||||
api.starrItem(id).enqueue(object : Callback<SuccessResponse> {
|
||||
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {}
|
||||
|
||||
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
|
||||
saveBtn.isLiked = false
|
||||
mView.favButton.isLiked = false
|
||||
Toast.makeText(c, R.string.cant_mark_favortie, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
})
|
||||
@ -198,19 +203,19 @@ class ItemListAdapter(private val app: Activity,
|
||||
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {}
|
||||
|
||||
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
|
||||
saveBtn.isLiked = true
|
||||
mView.favButton.isLiked = true
|
||||
Toast.makeText(c, R.string.cant_unmark_favortie, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
shareBtn.setOnClickListener {
|
||||
mView.shareBtn.setOnClickListener {
|
||||
c.shareLink(items[adapterPosition].getLinkDecoded())
|
||||
}
|
||||
|
||||
browserBtn.setOnClickListener {
|
||||
c.openInBrowser(items[adapterPosition])
|
||||
mView.browserBtn.setOnClickListener {
|
||||
c.openInBrowserAsNewTask(items[adapterPosition])
|
||||
|
||||
}
|
||||
}
|
||||
@ -223,11 +228,15 @@ class ItemListAdapter(private val app: Activity,
|
||||
|
||||
if (!clickBehavior) {
|
||||
mView.setOnClickListener {
|
||||
c.openItemUrl(items[adapterPosition],
|
||||
customTabsIntent,
|
||||
internalBrowser,
|
||||
articleViewer,
|
||||
app)
|
||||
c.openItemUrl(items[adapterPosition].getLinkDecoded(),
|
||||
items[adapterPosition].content,
|
||||
items[adapterPosition].getThumbnail(c),
|
||||
items[adapterPosition].title,
|
||||
items[adapterPosition].sourceAndDateText(),
|
||||
customTabsIntent,
|
||||
internalBrowser,
|
||||
articleViewer,
|
||||
app)
|
||||
}
|
||||
mView.setOnLongClickListener {
|
||||
actionBarShowHide()
|
||||
@ -236,11 +245,15 @@ class ItemListAdapter(private val app: Activity,
|
||||
} else {
|
||||
mView.setOnClickListener { actionBarShowHide() }
|
||||
mView.setOnLongClickListener {
|
||||
c.openItemUrl(items[adapterPosition],
|
||||
customTabsIntent,
|
||||
internalBrowser,
|
||||
articleViewer,
|
||||
app)
|
||||
c.openItemUrl(items[adapterPosition].getLinkDecoded(),
|
||||
items[adapterPosition].content,
|
||||
items[adapterPosition].getThumbnail(c),
|
||||
items[adapterPosition].title,
|
||||
items[adapterPosition].sourceAndDateText(),
|
||||
customTabsIntent,
|
||||
internalBrowser,
|
||||
articleViewer,
|
||||
app)
|
||||
true
|
||||
}
|
||||
}
|
||||
@ -248,7 +261,11 @@ class ItemListAdapter(private val app: Activity,
|
||||
|
||||
private fun actionBarShowHide() {
|
||||
bars[adapterPosition] = true
|
||||
if (actionBar.visibility == View.GONE) actionBar.visibility = View.VISIBLE else actionBar.visibility = View.GONE
|
||||
if (mView.actionBar.visibility == View.GONE) {
|
||||
mView.actionBar.visibility = View.VISIBLE
|
||||
} else {
|
||||
mView.actionBar.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,19 +10,18 @@ import android.widget.Button
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
|
||||
import apps.amine.bou.readerforselfoss.R
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.Sources
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
|
||||
import apps.amine.bou.readerforselfoss.utils.glide.circularBitmapDrawable
|
||||
import apps.amine.bou.readerforselfoss.utils.toTextDrawableString
|
||||
import com.amulyakhare.textdrawable.TextDrawable
|
||||
import com.amulyakhare.textdrawable.util.ColorGenerator
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
|
||||
import apps.amine.bou.readerforselfoss.R
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.Sources
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
|
||||
import apps.amine.bou.readerforselfoss.utils.circularBitmapDrawable
|
||||
import apps.amine.bou.readerforselfoss.utils.toTextDrawableString
|
||||
import kotlinx.android.synthetic.main.source_list_item.view.*
|
||||
|
||||
|
||||
class SourcesListAdapter(private val app: Activity,
|
||||
@ -43,13 +42,13 @@ class SourcesListAdapter(private val app: Activity,
|
||||
val color = generator.getColor(itm.title)
|
||||
|
||||
val drawable =
|
||||
TextDrawable
|
||||
.builder()
|
||||
.round()
|
||||
.build(itm.title.toTextDrawableString(), color)
|
||||
holder.sourceImage.setImageDrawable(drawable)
|
||||
TextDrawable
|
||||
.builder()
|
||||
.round()
|
||||
.build(itm.title.toTextDrawableString(), color)
|
||||
holder.itemImage.setImageDrawable(drawable)
|
||||
} else {
|
||||
c.circularBitmapDrawable(itm.getIcon(c), holder.sourceImage)
|
||||
c.circularBitmapDrawable(itm.getIcon(c), holder.itemImage)
|
||||
}
|
||||
|
||||
holder.sourceTitle.text = itm.title
|
||||
@ -60,17 +59,14 @@ class SourcesListAdapter(private val app: Activity,
|
||||
}
|
||||
|
||||
inner class ViewHolder(internal val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) {
|
||||
lateinit var sourceImage: ImageView
|
||||
lateinit var itemImage: ImageView
|
||||
lateinit var sourceTitle: TextView
|
||||
|
||||
init {
|
||||
|
||||
handleClickListeners()
|
||||
}
|
||||
|
||||
private fun handleClickListeners() {
|
||||
sourceImage = mView.findViewById(R.id.itemImage)
|
||||
sourceTitle = mView.findViewById(R.id.sourceTitle)
|
||||
|
||||
val deleteBtn: Button = mView.findViewById(R.id.deleteBtn)
|
||||
|
||||
|
@ -9,13 +9,16 @@ import retrofit2.converter.gson.GsonConverterFactory
|
||||
|
||||
|
||||
|
||||
class MercuryApi(private val key: String) {
|
||||
class MercuryApi(private val key: String, shouldLog: Boolean) {
|
||||
private val service: MercuryService
|
||||
|
||||
init {
|
||||
|
||||
val interceptor = HttpLoggingInterceptor()
|
||||
interceptor.level = HttpLoggingInterceptor.Level.BODY
|
||||
interceptor.level = if (shouldLog)
|
||||
HttpLoggingInterceptor.Level.BODY
|
||||
else
|
||||
HttpLoggingInterceptor.Level.NONE
|
||||
val client = OkHttpClient.Builder().addInterceptor(interceptor).build()
|
||||
|
||||
val gson = GsonBuilder()
|
||||
|
@ -2,20 +2,20 @@ package apps.amine.bou.readerforselfoss.api.mercury
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
|
||||
|
||||
class ParsedContent(val title: String,
|
||||
val content: String,
|
||||
val date_published: String,
|
||||
val lead_image_url: String,
|
||||
val dek: String,
|
||||
val url: String,
|
||||
val domain: String,
|
||||
val excerpt: String,
|
||||
val total_pages: Int,
|
||||
val rendered_pages: Int,
|
||||
val next_page_url: String) : Parcelable {
|
||||
class ParsedContent(@SerializedName("title") val title: String,
|
||||
@SerializedName("content") val content: String,
|
||||
@SerializedName("date_published") val date_published: String,
|
||||
@SerializedName("lead_image_url") val lead_image_url: String,
|
||||
@SerializedName("dek") val dek: String,
|
||||
@SerializedName("url") val url: String,
|
||||
@SerializedName("domain") val domain: String,
|
||||
@SerializedName("excerpt") val excerpt: String,
|
||||
@SerializedName("total_pages") val total_pages: Int,
|
||||
@SerializedName("rendered_pages") val rendered_pages: Int,
|
||||
@SerializedName("next_page_url") val next_page_url: String) : Parcelable {
|
||||
|
||||
companion object {
|
||||
@JvmField val CREATOR: Parcelable.Creator<ParsedContent> = object : Parcelable.Creator<ParsedContent> {
|
||||
|
@ -1,12 +1,10 @@
|
||||
package apps.amine.bou.readerforselfoss.api.selfoss
|
||||
|
||||
import java.lang.reflect.Type
|
||||
|
||||
import com.google.gson.JsonParseException
|
||||
import com.google.gson.JsonDeserializationContext
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonDeserializer
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonParseException
|
||||
import java.lang.reflect.Type
|
||||
|
||||
|
||||
internal class BooleanTypeAdapter : JsonDeserializer<Boolean> {
|
||||
|
@ -2,13 +2,8 @@ package apps.amine.bou.readerforselfoss.api.selfoss
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.widget.Toast
|
||||
import apps.amine.bou.readerforselfoss.LoginActivity
|
||||
import apps.amine.bou.readerforselfoss.R
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
import apps.amine.bou.readerforselfoss.utils.Config
|
||||
import apps.amine.bou.readerforselfoss.utils.getUnsafeHttpClient
|
||||
import com.burgstaller.okhttp.AuthenticationCacheInterceptor
|
||||
import com.burgstaller.okhttp.CachingAuthenticatorDecorator
|
||||
import com.burgstaller.okhttp.DispatchingAuthenticator
|
||||
@ -18,34 +13,40 @@ import com.burgstaller.okhttp.digest.Credentials
|
||||
import com.burgstaller.okhttp.digest.DigestAuthenticator
|
||||
import com.google.gson.GsonBuilder
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Call
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
|
||||
import apps.amine.bou.readerforselfoss.utils.Config
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
|
||||
// codebeat:disable[ARITY,TOO_MANY_FUNCTIONS]
|
||||
class SelfossApi(c: Context, callingActivity: Activity) {
|
||||
class SelfossApi(c: Context, callingActivity: Activity, isWithSelfSignedCert: Boolean, shouldLog: Boolean) {
|
||||
|
||||
private lateinit var service: SelfossService
|
||||
private val config: Config = Config(c)
|
||||
private val userName: String
|
||||
private val password: String
|
||||
|
||||
fun OkHttpClient.Builder.maybeWithSelfSigned(isWithSelfSignedCert: Boolean): OkHttpClient.Builder =
|
||||
if (isWithSelfSignedCert) {
|
||||
getUnsafeHttpClient()
|
||||
} else {
|
||||
this
|
||||
}
|
||||
|
||||
fun Credentials.createAuthenticator(): DispatchingAuthenticator =
|
||||
DispatchingAuthenticator.Builder()
|
||||
.with("digest", DigestAuthenticator(this))
|
||||
.with("basic", BasicAuthenticator(this))
|
||||
.build()
|
||||
|
||||
fun DispatchingAuthenticator.getHttpClien(): OkHttpClient {
|
||||
fun DispatchingAuthenticator.getHttpClien(isWithSelfSignedCert: Boolean): OkHttpClient.Builder {
|
||||
val authCache = ConcurrentHashMap<String, CachingAuthenticator>()
|
||||
return OkHttpClient
|
||||
.Builder()
|
||||
.maybeWithSelfSigned(isWithSelfSignedCert)
|
||||
.authenticator(CachingAuthenticatorDecorator(this, authCache))
|
||||
.addInterceptor(AuthenticationCacheInterceptor(authCache))
|
||||
.build()
|
||||
}
|
||||
|
||||
|
||||
@ -65,13 +66,23 @@ class SelfossApi(c: Context, callingActivity: Activity) {
|
||||
.setLenient()
|
||||
.create()
|
||||
|
||||
val logging = HttpLoggingInterceptor()
|
||||
|
||||
logging.level = if (shouldLog)
|
||||
HttpLoggingInterceptor.Level.BODY
|
||||
else
|
||||
HttpLoggingInterceptor.Level.NONE
|
||||
|
||||
val httpClient = authenticator.getHttpClien(isWithSelfSignedCert)
|
||||
|
||||
httpClient.addInterceptor(logging)
|
||||
|
||||
try {
|
||||
val retrofit =
|
||||
Retrofit
|
||||
.Builder()
|
||||
.baseUrl(config.baseUrl)
|
||||
.client(authenticator.getHttpClien())
|
||||
.client(httpClient.build())
|
||||
.addConverterFactory(GsonConverterFactory.create(gson))
|
||||
.build()
|
||||
service = retrofit.create(SelfossService::class.java)
|
||||
@ -83,17 +94,17 @@ class SelfossApi(c: Context, callingActivity: Activity) {
|
||||
fun login(): Call<SuccessResponse> =
|
||||
service.loginToSelfoss(config.userLogin, config.userPassword)
|
||||
|
||||
fun readItems(tag: String?, sourceId: Long?, search: String?): Call<List<Item>> =
|
||||
getItems("read", tag, sourceId, search)
|
||||
fun readItems(tag: String?, sourceId: Long?, search: String?, itemsNumber: Int, offset: Int): Call<List<Item>> =
|
||||
getItems("read", tag, sourceId, search, itemsNumber, offset)
|
||||
|
||||
fun newItems(tag: String?, sourceId: Long?, search: String?): Call<List<Item>> =
|
||||
getItems("unread", tag, sourceId, search)
|
||||
fun newItems(tag: String?, sourceId: Long?, search: String?, itemsNumber: Int, offset: Int): Call<List<Item>> =
|
||||
getItems("unread", tag, sourceId, search, itemsNumber, offset)
|
||||
|
||||
fun starredItems(tag: String?, sourceId: Long?, search: String?): Call<List<Item>> =
|
||||
getItems("starred", tag, sourceId, search)
|
||||
fun starredItems(tag: String?, sourceId: Long?, search: String?, itemsNumber: Int, offset: Int): Call<List<Item>> =
|
||||
getItems("starred", tag, sourceId, search, itemsNumber, offset)
|
||||
|
||||
private fun getItems(type: String, tag: String?, sourceId: Long?, search: String?): Call<List<Item>> =
|
||||
service.getItems(type, tag, sourceId, search, userName, password)
|
||||
private fun getItems(type: String, tag: String?, sourceId: Long?, search: String?, items: Int, offset: Int): Call<List<Item>> =
|
||||
service.getItems(type, tag, sourceId, search, userName, password, items, offset)
|
||||
|
||||
fun markItem(itemId: String): Call<SuccessResponse> =
|
||||
service.markAsRead(itemId, userName, password)
|
||||
@ -132,5 +143,3 @@ class SelfossApi(c: Context, callingActivity: Activity) {
|
||||
service.createSource(title, url, spout, tags, filter, userName, password)
|
||||
|
||||
}
|
||||
|
||||
// codebeat:enable[ARITY,TOO_MANY_FUNCTIONS]
|
@ -7,7 +7,7 @@ import android.os.Parcelable
|
||||
|
||||
import apps.amine.bou.readerforselfoss.utils.Config
|
||||
import apps.amine.bou.readerforselfoss.utils.isEmptyOrNullOrNullString
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
|
||||
private fun constructUrl(config: Config?, path: String, file: String): String {
|
||||
@ -19,23 +19,28 @@ private fun constructUrl(config: Config?, path: String, file: String): String {
|
||||
}
|
||||
|
||||
|
||||
data class Tag(val tag: String, val color: String, val unread: Int)
|
||||
data class Tag(@SerializedName("tag") val tag: String,
|
||||
@SerializedName("color") val color: String,
|
||||
@SerializedName("unread") val unread: Int)
|
||||
|
||||
class SuccessResponse(val success: Boolean) {
|
||||
class SuccessResponse(@SerializedName("success") val success: Boolean) {
|
||||
val isSuccess: Boolean
|
||||
get() = success
|
||||
}
|
||||
|
||||
class Stats(val total: Int, val unread: Int, val starred: Int)
|
||||
class Stats(@SerializedName("total") val total: Int,
|
||||
@SerializedName("unread") val unread: Int,
|
||||
@SerializedName("starred") val starred: Int)
|
||||
|
||||
data class Spout(val name: String, val description: String)
|
||||
data class Spout(@SerializedName("name") val name: String,
|
||||
@SerializedName("description") val description: String)
|
||||
|
||||
data class Sources(val id: String,
|
||||
val title: String,
|
||||
val tags: String,
|
||||
val spout: String,
|
||||
val error: String,
|
||||
val icon: String) {
|
||||
data class Sources(@SerializedName("id") val id: String,
|
||||
@SerializedName("title") val title: String,
|
||||
@SerializedName("tags") val tags: String,
|
||||
@SerializedName("spout") val spout: String,
|
||||
@SerializedName("error") val error: String,
|
||||
@SerializedName("icon") val icon: String) {
|
||||
var config: Config? = null
|
||||
|
||||
fun getIcon(app: Context): String {
|
||||
@ -47,15 +52,16 @@ data class Sources(val id: String,
|
||||
|
||||
}
|
||||
|
||||
data class Item(val id: String,
|
||||
val datetime: String,
|
||||
val title: String,
|
||||
val unread: Boolean,
|
||||
val starred: Boolean,
|
||||
val thumbnail: String,
|
||||
val icon: String,
|
||||
val link: String,
|
||||
val sourcetitle: String) : Parcelable {
|
||||
data class Item(@SerializedName("id") val id: String,
|
||||
@SerializedName("datetime") val datetime: String,
|
||||
@SerializedName("title") val title: String,
|
||||
@SerializedName("content") val content: String,
|
||||
@SerializedName("unread") val unread: Boolean,
|
||||
@SerializedName("starred") val starred: Boolean,
|
||||
@SerializedName("thumbnail") val thumbnail: String,
|
||||
@SerializedName("icon") val icon: String,
|
||||
@SerializedName("link") val link: String,
|
||||
@SerializedName("sourcetitle") val sourcetitle: String) : Parcelable {
|
||||
|
||||
var config: Config? = null
|
||||
|
||||
@ -67,15 +73,16 @@ data class Item(val id: String,
|
||||
}
|
||||
|
||||
constructor(source: Parcel) : this(
|
||||
id = source.readString(),
|
||||
datetime = source.readString(),
|
||||
title = source.readString(),
|
||||
unread = 0.toByte() != source.readByte(),
|
||||
starred = 0.toByte() != source.readByte(),
|
||||
thumbnail = source.readString(),
|
||||
icon = source.readString(),
|
||||
link = source.readString(),
|
||||
sourcetitle = source.readString()
|
||||
id = source.readString(),
|
||||
datetime = source.readString(),
|
||||
title = source.readString(),
|
||||
content = source.readString(),
|
||||
unread = 0.toByte() != source.readByte(),
|
||||
starred = 0.toByte() != source.readByte(),
|
||||
thumbnail = source.readString(),
|
||||
icon = source.readString(),
|
||||
link = source.readString(),
|
||||
sourcetitle = source.readString()
|
||||
)
|
||||
|
||||
override fun describeContents() = 0
|
||||
@ -84,6 +91,7 @@ data class Item(val id: String,
|
||||
dest.writeString(id)
|
||||
dest.writeString(datetime)
|
||||
dest.writeString(title)
|
||||
dest.writeString(content)
|
||||
dest.writeByte((if (unread) 1 else 0))
|
||||
dest.writeByte((if (starred) 1 else 0))
|
||||
dest.writeString(thumbnail)
|
||||
@ -109,20 +117,26 @@ data class Item(val id: String,
|
||||
// TODO: maybe find a better way to handle these kind of urls
|
||||
fun getLinkDecoded(): String {
|
||||
var stringUrl: String
|
||||
if (link.startsWith("http://news.google.com/news/") || link.startsWith("https://news.google.com/news/")) {
|
||||
stringUrl = if (link.startsWith("http://news.google.com/news/") || link.startsWith("https://news.google.com/news/")) {
|
||||
if (link.contains("&url=")) {
|
||||
stringUrl = link.substringAfter("&url=")
|
||||
link.substringAfter("&url=")
|
||||
} else {
|
||||
stringUrl = this.link.replace("&", "&")
|
||||
this.link.replace("&", "&")
|
||||
}
|
||||
} else {
|
||||
stringUrl = this.link.replace("&", "&")
|
||||
this.link.replace("&", "&")
|
||||
}
|
||||
|
||||
// handle :443 => https
|
||||
if (stringUrl.contains(":443")) {
|
||||
stringUrl = stringUrl.replace(":443", "").replace("http://", "https://")
|
||||
}
|
||||
|
||||
// handle url not starting with http
|
||||
if (stringUrl.startsWith("//")) {
|
||||
stringUrl = "http:" + stringUrl
|
||||
}
|
||||
|
||||
return stringUrl
|
||||
}
|
||||
|
||||
|
@ -5,36 +5,43 @@ import retrofit2.http.DELETE
|
||||
import retrofit2.http.Field
|
||||
import retrofit2.http.FormUrlEncoded
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Headers
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Path
|
||||
import retrofit2.http.Query
|
||||
|
||||
|
||||
|
||||
// codebeat:disable[ARITY]
|
||||
internal interface SelfossService {
|
||||
|
||||
@GET("login")
|
||||
fun loginToSelfoss(@Query("username") username: String, @Query("password") password: String): Call<SuccessResponse>
|
||||
|
||||
|
||||
@GET("items")
|
||||
fun getItems(@Query("type") type: String,
|
||||
@Query("tag") tag: String?,
|
||||
@Query("source") source: Long?,
|
||||
@Query("search") search: String?,
|
||||
@Query("username") username: String,
|
||||
@Query("password") password: String): Call<List<Item>>
|
||||
@Query("password") password: String,
|
||||
@Query("items") items: Int,
|
||||
@Query("offset") offset: Int): Call<List<Item>>
|
||||
|
||||
|
||||
@Headers("Content-Type: application/x-www-form-urlencoded")
|
||||
@POST("mark/{id}")
|
||||
fun markAsRead(@Path("id") id: String,
|
||||
@Query("username") username: String,
|
||||
@Query("password") password: String): Call<SuccessResponse>
|
||||
|
||||
|
||||
@Headers("Content-Type: application/x-www-form-urlencoded")
|
||||
@POST("unmark/{id}")
|
||||
fun unmarkAsRead(@Path("id") id: String,
|
||||
@Query("username") username: String,
|
||||
@Query("password") password: String): Call<SuccessResponse>
|
||||
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST("mark")
|
||||
fun markAllAsRead(@Field("ids[]") ids: List<String>,
|
||||
@ -42,12 +49,14 @@ internal interface SelfossService {
|
||||
@Query("password") password: String): Call<SuccessResponse>
|
||||
|
||||
|
||||
@Headers("Content-Type: application/x-www-form-urlencoded")
|
||||
@POST("starr/{id}")
|
||||
fun starr(@Path("id") id: String,
|
||||
@Query("username") username: String,
|
||||
@Query("password") password: String): Call<SuccessResponse>
|
||||
|
||||
|
||||
@Headers("Content-Type: application/x-www-form-urlencoded")
|
||||
@POST("unstarr/{id}")
|
||||
fun unstarr(@Path("id") id: String,
|
||||
@Query("username") username: String,
|
||||
@ -68,10 +77,12 @@ internal interface SelfossService {
|
||||
fun update(@Query("username") username: String,
|
||||
@Query("password") password: String): Call<String>
|
||||
|
||||
|
||||
@GET("sources/spouts")
|
||||
fun spouts(@Query("username") username: String,
|
||||
@Query("password") password: String): Call<Map<String, Spout>>
|
||||
|
||||
|
||||
@GET("sources/list")
|
||||
fun sources(@Query("username") username: String,
|
||||
@Query("password") password: String): Call<List<Sources>>
|
||||
@ -82,6 +93,7 @@ internal interface SelfossService {
|
||||
@Query("username") username: String,
|
||||
@Query("password") password: String): Call<SuccessResponse>
|
||||
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST("source")
|
||||
fun createSource(@Field("title") title: String,
|
||||
@ -92,4 +104,3 @@ internal interface SelfossService {
|
||||
@Query("username") username: String,
|
||||
@Query("password") password: String): Call<SuccessResponse>
|
||||
}
|
||||
// codebeat:disable[ARITY]
|
@ -24,7 +24,7 @@ import com.ftinc.scoop.Scoop;
|
||||
* A {@link PreferenceActivity} which implements and proxies the necessary calls
|
||||
* to be used with AppCompat.
|
||||
*/
|
||||
public abstract class AppCompatPreferenceActivity extends PreferenceActivity {
|
||||
public abstract class AppCompatPreferenceActivity extends PreferenceActivity { //NOSONAR
|
||||
|
||||
private AppCompatDelegate mDelegate;
|
||||
|
||||
@ -116,6 +116,7 @@ public abstract class AppCompatPreferenceActivity extends PreferenceActivity {
|
||||
getDelegate().onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateOptionsMenu() {
|
||||
getDelegate().invalidateOptionsMenu();
|
||||
}
|
||||
|
@ -1,25 +1,35 @@
|
||||
package apps.amine.bou.readerforselfoss.settings;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.preference.EditTextPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.Preference.OnPreferenceChangeListener;
|
||||
import android.preference.Preference.OnPreferenceClickListener;
|
||||
import android.preference.PreferenceActivity;
|
||||
import android.preference.SwitchPreference;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.preference.SwitchPreference;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.text.InputFilter;
|
||||
import android.text.Spanned;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import apps.amine.bou.readerforselfoss.BuildConfig;
|
||||
import apps.amine.bou.readerforselfoss.R;
|
||||
import apps.amine.bou.readerforselfoss.utils.Config;
|
||||
import com.ftinc.scoop.ui.ScoopSettingsActivity;
|
||||
|
||||
|
||||
@ -34,7 +44,7 @@ import com.ftinc.scoop.ui.ScoopSettingsActivity;
|
||||
* href="http://developer.android.com/guide/topics/ui/settings.html">Settings
|
||||
* API Guide</a> for more information on developing a Settings UI.
|
||||
*/
|
||||
public class SettingsActivity extends AppCompatPreferenceActivity {
|
||||
public class SettingsActivity extends AppCompatPreferenceActivity { //NOSONAR
|
||||
/**
|
||||
* A preference value change listener that updates the preference's summary
|
||||
* to reflect its new value.
|
||||
@ -116,9 +126,11 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
||||
* This method stops fragment injection in malicious applications.
|
||||
* Make sure to deny any unknown fragments here.
|
||||
*/
|
||||
@Override
|
||||
protected boolean isValidFragment(String fragmentName) {
|
||||
return PreferenceFragment.class.getName().equals(fragmentName)
|
||||
|| GeneralPreferenceFragment.class.getName().equals(fragmentName)
|
||||
|| DebugPreferenceFragment.class.getName().equals(fragmentName)
|
||||
|| LinksPreferenceFragment.class.getName().equals(fragmentName);
|
||||
}
|
||||
|
||||
@ -144,6 +156,63 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
EditTextPreference itemsNumber = (EditTextPreference) findPreference("prefer_api_items_number");
|
||||
itemsNumber.getEditText().setFilters(new InputFilter[]{
|
||||
new InputFilter (){
|
||||
|
||||
@Override
|
||||
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
|
||||
try {
|
||||
int input = Integer.parseInt(dest.toString() + source.toString());
|
||||
if (input <= 200 && input >0)
|
||||
return null;
|
||||
} catch (NumberFormatException nfe) {
|
||||
Toast.makeText(getActivity(), R.string.items_number_should_be_number, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
if (id == android.R.id.home) {
|
||||
getActivity().finish();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
public static class DebugPreferenceFragment extends PreferenceFragment {
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
addPreferencesFromResource(R.xml.pref_debug);
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
SharedPreferences pref = getActivity().getSharedPreferences(Config.Companion.getSettingsName(), Context.MODE_PRIVATE);
|
||||
final String id = pref.getString("unique_id", "...");
|
||||
|
||||
final Preference identifier = findPreference("debug_identifier");
|
||||
final ClipboardManager clipboard = (ClipboardManager)
|
||||
getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
|
||||
identifier.setOnPreferenceClickListener(new OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
ClipData clip = ClipData.newPlainText("Selfoss unique id", id);
|
||||
clipboard.setPrimaryClip(clip);
|
||||
|
||||
Toast.makeText(getActivity(), R.string.unique_id_to_clipboard, Toast.LENGTH_LONG).show();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
identifier.setTitle(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -177,7 +246,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
||||
findPreference( "trackerLink" ).setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
openUrl(Uri.parse(getString(R.string.tracker_url)));
|
||||
openUrl(Uri.parse(BuildConfig.TRACKER_URL));
|
||||
return true;
|
||||
}
|
||||
});
|
||||
@ -185,7 +254,15 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
||||
findPreference("sourceLink").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
openUrl(Uri.parse(getString(R.string.source_url)));
|
||||
openUrl(Uri.parse(BuildConfig.SOURCE_URL));
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
findPreference("translation").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
openUrl(Uri.parse(BuildConfig.TRANSLATION_URL));
|
||||
return false;
|
||||
}
|
||||
});
|
||||
@ -206,7 +283,9 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
||||
public void onHeaderClick(Header header, int position) {
|
||||
super.onHeaderClick(header, position);
|
||||
if (header.id == R.id.theme_change) {
|
||||
getBaseContext().startActivity(ScoopSettingsActivity.createIntent(getApplicationContext()));
|
||||
Intent intent = ScoopSettingsActivity.createIntent(getApplicationContext());
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
getApplicationContext().startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
@ -5,8 +5,6 @@ import android.content.Context
|
||||
import android.support.annotation.ColorInt
|
||||
import android.util.TypedValue
|
||||
import apps.amine.bou.readerforselfoss.R
|
||||
import java.lang.reflect.AccessibleObject.setAccessible
|
||||
|
||||
|
||||
|
||||
class AppColors(a: Activity) {
|
||||
|
@ -0,0 +1,7 @@
|
||||
package apps.amine.bou.readerforselfoss.utils
|
||||
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
|
||||
import retrofit2.Response
|
||||
|
||||
fun Response<SuccessResponse>.succeeded(): Boolean =
|
||||
this.code() === 200 && this.body() != null && this.body()!!.isSuccess
|
@ -5,48 +5,10 @@ import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.text.TextUtils
|
||||
import android.util.Patterns
|
||||
|
||||
import com.google.firebase.remoteconfig.FirebaseRemoteConfig
|
||||
import okhttp3.HttpUrl
|
||||
|
||||
import apps.amine.bou.readerforselfoss.BuildConfig
|
||||
import apps.amine.bou.readerforselfoss.R
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.Item
|
||||
import com.google.firebase.remoteconfig.FirebaseRemoteConfig
|
||||
|
||||
|
||||
fun Context.checkAndDisplayStoreApk() = {
|
||||
fun isStoreVersion(): Boolean =
|
||||
try {
|
||||
val installer = this.packageManager
|
||||
.getInstallerPackageName(this.packageName)
|
||||
!TextUtils.isEmpty(installer)
|
||||
} catch (e: Throwable) {
|
||||
false
|
||||
}
|
||||
|
||||
if (!isStoreVersion() && !BuildConfig.GITHUB_VERSION) {
|
||||
val alertDialog = AlertDialog.Builder(this).create()
|
||||
alertDialog.setTitle(getString(R.string.warning_version))
|
||||
alertDialog.setMessage(getString(R.string.text_version))
|
||||
alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "OK",
|
||||
{ dialog, _ -> dialog.dismiss() })
|
||||
alertDialog.show()
|
||||
} else Unit
|
||||
}
|
||||
|
||||
fun String.isUrlValid(): Boolean {
|
||||
val baseUrl = HttpUrl.parse(this)
|
||||
var existsAndEndsWithSlash = false
|
||||
if (baseUrl != null) {
|
||||
val pathSegments = baseUrl.pathSegments()
|
||||
existsAndEndsWithSlash = "" == pathSegments[pathSegments.size - 1]
|
||||
}
|
||||
|
||||
return Patterns.WEB_URL.matcher(this).matches() && existsAndEndsWithSlash
|
||||
}
|
||||
|
||||
fun String?.isEmptyOrNullOrNullString(): Boolean =
|
||||
this == null || this == "null" || this.isEmpty()
|
||||
|
||||
@ -94,7 +56,7 @@ fun String.longHash(): Long {
|
||||
val l = this.length
|
||||
val chars = this.toCharArray()
|
||||
|
||||
for (i in 0..l - 1) {
|
||||
for (i in 0 until l) {
|
||||
h = 31 * h + chars[i].toLong()
|
||||
}
|
||||
return h
|
||||
@ -113,11 +75,4 @@ fun Context.shareLink(itemUrl: String) {
|
||||
sendIntent.putExtra(Intent.EXTRA_TEXT, itemUrl.toStringUriWithHttp())
|
||||
sendIntent.type = "text/plain"
|
||||
startActivity(Intent.createChooser(sendIntent, getString(R.string.share)).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
|
||||
}
|
||||
|
||||
fun Context.openInBrowser(i: Item) {
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
intent.data = Uri.parse(i.getLinkDecoded().toStringUriWithHttp())
|
||||
startActivity(intent)
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
package apps.amine.bou.readerforselfoss.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory
|
||||
import android.widget.ImageView
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.target.BitmapImageViewTarget
|
||||
|
||||
|
||||
fun Context.bitmapCenterCrop(url: String, iv: ImageView) =
|
||||
Glide.with(this).load(url).asBitmap().centerCrop().into(iv)
|
||||
|
||||
fun Context.bitmapFitCenter(url: String, iv: ImageView) =
|
||||
Glide.with(this).load(url).asBitmap().fitCenter().into(iv)
|
||||
|
||||
fun Context.circularBitmapDrawable(url: String, iv: ImageView) =
|
||||
Glide.with(this).load(url).asBitmap().centerCrop().into(object : BitmapImageViewTarget(iv) {
|
||||
override fun setResource(resource: Bitmap) {
|
||||
val circularBitmapDrawable = RoundedBitmapDrawableFactory.create(resources, resource)
|
||||
circularBitmapDrawable.isCircular = true
|
||||
iv.setImageDrawable(circularBitmapDrawable)
|
||||
}
|
||||
})
|
@ -0,0 +1,39 @@
|
||||
package apps.amine.bou.readerforselfoss.utils
|
||||
|
||||
import okhttp3.OkHttpClient
|
||||
import java.security.cert.CertificateException
|
||||
import java.security.cert.X509Certificate
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.TrustManager
|
||||
import javax.net.ssl.X509TrustManager
|
||||
|
||||
fun getUnsafeHttpClient() =
|
||||
try {
|
||||
// Create a trust manager that does not validate certificate chains
|
||||
val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager {
|
||||
override fun getAcceptedIssuers(): Array<X509Certificate> =
|
||||
arrayOf()
|
||||
|
||||
@Throws(CertificateException::class)
|
||||
override fun checkClientTrusted(chain: Array<java.security.cert.X509Certificate>, authType: String) {
|
||||
}
|
||||
|
||||
@Throws(CertificateException::class)
|
||||
override fun checkServerTrusted(chain: Array<java.security.cert.X509Certificate>, authType: String) {
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
// Install the all-trusting trust manager
|
||||
val sslContext = SSLContext.getInstance("SSL")
|
||||
sslContext.init(null, trustAllCerts, java.security.SecureRandom())
|
||||
|
||||
val sslSocketFactory = sslContext.socketFactory
|
||||
|
||||
OkHttpClient.Builder()
|
||||
.sslSocketFactory(sslSocketFactory, trustAllCerts[0] as X509TrustManager)
|
||||
.hostnameVerifier { _, _ -> true }
|
||||
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException(e)
|
||||
}
|
@ -16,7 +16,7 @@ fun String.toTextDrawableString(): String {
|
||||
}
|
||||
|
||||
fun Item.sourceAndDateText(): String {
|
||||
var formattedDate: String = try {
|
||||
val formattedDate: String = try {
|
||||
" " + DateUtils.getRelativeTimeSpanString(
|
||||
SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(this.datetime).time,
|
||||
Date().time,
|
||||
|
@ -7,14 +7,14 @@ import android.content.Intent
|
||||
import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
import android.support.customtabs.CustomTabsIntent
|
||||
|
||||
import xyz.klinker.android.drag_dismiss.DragDismissIntentBuilder
|
||||
|
||||
import android.util.Patterns
|
||||
import android.widget.Toast
|
||||
import apps.amine.bou.readerforselfoss.R
|
||||
import apps.amine.bou.readerforselfoss.ReaderActivity
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.Item
|
||||
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
|
||||
|
||||
import okhttp3.HttpUrl
|
||||
import xyz.klinker.android.drag_dismiss.DragDismissIntentBuilder
|
||||
|
||||
|
||||
fun Context.buildCustomTabsIntent(): CustomTabsIntent {
|
||||
@ -50,33 +50,88 @@ fun Context.buildCustomTabsIntent(): CustomTabsIntent {
|
||||
return intentBuilder.build()
|
||||
}
|
||||
|
||||
fun Context.openItemUrl(i: Item,
|
||||
customTabsIntent: CustomTabsIntent,
|
||||
internalBrowser: Boolean,
|
||||
articleViewer: Boolean,
|
||||
app: Activity) {
|
||||
if (!internalBrowser) {
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.data = Uri.parse(i.getLinkDecoded())
|
||||
fun Context.openItemUrlInternally(linkDecoded: String,
|
||||
content: String,
|
||||
image: String,
|
||||
title: String,
|
||||
source: String,
|
||||
customTabsIntent: CustomTabsIntent,
|
||||
articleViewer: Boolean,
|
||||
app: Activity) {
|
||||
if (articleViewer) {
|
||||
val intent = Intent(this, ReaderActivity::class.java)
|
||||
|
||||
/*DragDismissIntentBuilder(this)
|
||||
.setFullscreenOnTablets(true) // defaults to false, tablets will have padding on each side
|
||||
.setDragElasticity(DragDismissIntentBuilder.DragElasticity.NORMAL) // Larger elasticities will make it easier to dismiss.
|
||||
.setDrawUnderStatusBar(true)
|
||||
.build(intent)*/
|
||||
|
||||
|
||||
intent.putExtra("url", linkDecoded)
|
||||
intent.putExtra("content", content)
|
||||
intent.putExtra("title", title)
|
||||
intent.putExtra("image", image)
|
||||
intent.putExtra("source", source)
|
||||
app.startActivity(intent)
|
||||
} else {
|
||||
if (articleViewer) {
|
||||
val intent = Intent(this, ReaderActivity::class.java)
|
||||
|
||||
DragDismissIntentBuilder(this)
|
||||
.setFullscreenOnTablets(true) // defaults to false, tablets will have padding on each side
|
||||
.setDragElasticity(DragDismissIntentBuilder.DragElasticity.NORMAL) // Larger elasticities will make it easier to dismiss.
|
||||
.build(intent)
|
||||
|
||||
intent.putExtra("url", i.getLinkDecoded())
|
||||
app.startActivity(intent)
|
||||
} else {
|
||||
CustomTabActivityHelper.openCustomTab(app, customTabsIntent, Uri.parse(i.getLinkDecoded())
|
||||
try {
|
||||
CustomTabActivityHelper.openCustomTab(app, customTabsIntent, Uri.parse(linkDecoded)
|
||||
) { _, uri ->
|
||||
val intent = Intent(Intent.ACTION_VIEW, uri)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
startActivity(intent)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
openInBrowser(linkDecoded, app)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.openItemUrl(linkDecoded: String,
|
||||
content: String,
|
||||
image: String,
|
||||
title: String,
|
||||
source: String,
|
||||
customTabsIntent: CustomTabsIntent,
|
||||
internalBrowser: Boolean,
|
||||
articleViewer: Boolean,
|
||||
app: Activity) {
|
||||
|
||||
if (!linkDecoded.isUrlValid()) {
|
||||
Toast.makeText(this, this.getString(R.string.cant_open_invalid_url), Toast.LENGTH_LONG).show()
|
||||
} else {
|
||||
if (!internalBrowser) {
|
||||
openInBrowser(linkDecoded, app)
|
||||
} else {
|
||||
this.openItemUrlInternally(linkDecoded, content, image, title, source, customTabsIntent, articleViewer, app)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun openInBrowser(linkDecoded: String, app: Activity) {
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.data = Uri.parse(linkDecoded)
|
||||
app.startActivity(intent)
|
||||
}
|
||||
|
||||
fun String.isUrlValid(): Boolean =
|
||||
HttpUrl.parse(this) != null && Patterns.WEB_URL.matcher(this).matches()
|
||||
|
||||
fun String.isBaseUrlValid(): Boolean {
|
||||
val baseUrl = HttpUrl.parse(this)
|
||||
var existsAndEndsWithSlash = false
|
||||
if (baseUrl != null) {
|
||||
val pathSegments = baseUrl.pathSegments()
|
||||
existsAndEndsWithSlash = "" == pathSegments[pathSegments.size - 1]
|
||||
}
|
||||
|
||||
return Patterns.WEB_URL.matcher(this).matches() && existsAndEndsWithSlash
|
||||
}
|
||||
|
||||
fun Context.openInBrowserAsNewTask(i: Item) {
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
intent.data = Uri.parse(i.getLinkDecoded().toStringUriWithHttp())
|
||||
startActivity(intent)
|
||||
}
|
||||
|
@ -0,0 +1,33 @@
|
||||
package apps.amine.bou.readerforselfoss.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.support.design.widget.CoordinatorLayout
|
||||
import android.support.design.widget.FloatingActionButton
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
|
||||
class ScrollAwareFABBehavior(context: Context, attrs: AttributeSet) : CoordinatorLayout.Behavior<FloatingActionButton>() {
|
||||
|
||||
override fun onStartNestedScroll(coordinatorLayout: CoordinatorLayout, child: FloatingActionButton,
|
||||
directTargetChild: View, target: View, nestedScrollAxes: Int): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onNestedScroll(coordinatorLayout: CoordinatorLayout,
|
||||
child: FloatingActionButton,
|
||||
target: View, dxConsumed: Int, dyConsumed: Int,
|
||||
dxUnconsumed: Int, dyUnconsumed: Int) {
|
||||
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed)
|
||||
if (dyConsumed > 0 && child.visibility == View.VISIBLE) {
|
||||
child.hide(object : FloatingActionButton.OnVisibilityChangedListener() {
|
||||
override fun onHidden(fab: FloatingActionButton?) {
|
||||
super.onHidden(fab)
|
||||
fab!!.visibility = View.INVISIBLE
|
||||
}
|
||||
})
|
||||
|
||||
} else if (dyConsumed < 0 && child.visibility != View.VISIBLE) {
|
||||
child.show()
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package apps.amine.bou.readerforselfoss.utils.bottombar
|
||||
|
||||
import com.ashokvarma.bottomnavigation.TextBadgeItem
|
||||
|
||||
|
||||
fun TextBadgeItem.removeBadge(): TextBadgeItem {
|
||||
this.setText("")
|
||||
this.hide()
|
||||
return this
|
||||
}
|
||||
|
||||
fun TextBadgeItem.maybeShow(): TextBadgeItem =
|
||||
if (this.isHidden)
|
||||
this.show()
|
||||
else
|
||||
this
|
@ -1,5 +1,8 @@
|
||||
package apps.amine.bou.readerforselfoss.utils.customtabs;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
@ -8,8 +11,6 @@ import android.support.customtabs.CustomTabsIntent;
|
||||
import android.support.customtabs.CustomTabsServiceConnection;
|
||||
import android.support.customtabs.CustomTabsSession;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This is a helper class to manage the connection to the Custom Tabs Service.
|
||||
*/
|
||||
|
@ -1,5 +1,9 @@
|
||||
package apps.amine.bou.readerforselfoss.utils.customtabs;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
@ -9,10 +13,8 @@ import android.net.Uri;
|
||||
import android.support.customtabs.CustomTabsService;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import apps.amine.bou.readerforselfoss.utils.customtabs.helpers.KeepAliveService;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import apps.amine.bou.readerforselfoss.utils.customtabs.helpers.KeepAliveService;
|
||||
|
||||
@SuppressWarnings("ALL")
|
||||
class CustomTabsHelper {
|
||||
@ -101,7 +103,7 @@ class CustomTabsHelper {
|
||||
List<ResolveInfo> handlers = pm.queryIntentActivities(
|
||||
intent,
|
||||
PackageManager.GET_RESOLVED_FILTER);
|
||||
if (handlers == null || handlers.size() == 0) {
|
||||
if (handlers == null || handlers.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
for (ResolveInfo resolveInfo : handlers) {
|
||||
|
@ -1,11 +1,12 @@
|
||||
package apps.amine.bou.readerforselfoss.utils.customtabs;
|
||||
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.support.customtabs.CustomTabsClient;
|
||||
import android.support.customtabs.CustomTabsServiceConnection;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
/**
|
||||
* Implementation for the CustomTabsServiceConnection that avoids leaking the
|
||||
* ServiceConnectionCallback
|
||||
|
@ -5,14 +5,11 @@ import android.support.annotation.LayoutRes
|
||||
import android.support.annotation.StringRes
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
|
||||
import apps.amine.bou.readerforselfoss.R
|
||||
import com.mikepenz.materialdrawer.holder.BadgeStyle
|
||||
import com.mikepenz.materialdrawer.holder.StringHolder
|
||||
import com.mikepenz.materialdrawer.model.interfaces.ColorfulBadgeable
|
||||
|
||||
import apps.amine.bou.readerforselfoss.R
|
||||
|
||||
|
||||
|
||||
class CustomUrlPrimaryDrawerItem : CustomUrlBasePrimaryDrawerItem<CustomUrlPrimaryDrawerItem, CustomUrlPrimaryDrawerItem.ViewHolder>(), ColorfulBadgeable<CustomUrlPrimaryDrawerItem> {
|
||||
protected var mBadge: StringHolder = StringHolder("")
|
||||
|
@ -0,0 +1,29 @@
|
||||
package apps.amine.bou.readerforselfoss.utils.glide
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory
|
||||
import android.widget.ImageView
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import com.bumptech.glide.request.target.BitmapImageViewTarget
|
||||
|
||||
|
||||
fun Context.bitmapCenterCrop(url: String, iv: ImageView) =
|
||||
Glide.with(this).asBitmap().load(url).apply(RequestOptions.centerCropTransform()).into(iv)
|
||||
|
||||
fun Context.bitmapFitCenter(url: String, iv: ImageView) =
|
||||
Glide.with(this).asBitmap().load(url).apply(RequestOptions.fitCenterTransform()).into(iv)
|
||||
|
||||
fun Context.circularBitmapDrawable(url: String, iv: ImageView) =
|
||||
Glide.with(this)
|
||||
.asBitmap()
|
||||
.load(url)
|
||||
.apply(RequestOptions.centerCropTransform()).
|
||||
into(object : BitmapImageViewTarget(iv) {
|
||||
override fun setResource(resource: Bitmap?) {
|
||||
val circularBitmapDrawable = RoundedBitmapDrawableFactory.create(resources, resource)
|
||||
circularBitmapDrawable.isCircular = true
|
||||
iv.setImageDrawable(circularBitmapDrawable)
|
||||
}
|
||||
})
|
@ -0,0 +1,32 @@
|
||||
package apps.amine.bou.readerforselfoss.utils.glide
|
||||
|
||||
import android.content.Context
|
||||
import apps.amine.bou.readerforselfoss.utils.Config
|
||||
import apps.amine.bou.readerforselfoss.utils.getUnsafeHttpClient
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.GlideBuilder
|
||||
import com.bumptech.glide.Registry
|
||||
import com.bumptech.glide.load.model.GlideUrl
|
||||
import com.bumptech.glide.module.GlideModule
|
||||
import java.io.InputStream
|
||||
|
||||
|
||||
class SelfSignedGlideModule : GlideModule {
|
||||
|
||||
override fun applyOptions(context: Context?, builder: GlideBuilder?) {}
|
||||
|
||||
override fun registerComponents(context: Context?, glide: Glide?, registry: Registry?) {
|
||||
|
||||
if (context != null) {
|
||||
val pref = context?.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
|
||||
if (pref.getBoolean("isSelfSignedCert", false)) {
|
||||
val client = getUnsafeHttpClient().build()
|
||||
|
||||
registry?.append(GlideUrl::class.java, InputStream::class.java,
|
||||
com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader.Factory(client))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
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,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z"/>
|
||||
</vector>
|
BIN
app/src/main/res/drawable-hdpi/ic_add.png
Normal file
After Width: | Height: | Size: 134 B |
Before Width: | Height: | Size: 124 B |
BIN
app/src/main/res/drawable-hdpi/ic_bug_report.png
Normal file
After Width: | Height: | Size: 271 B |
BIN
app/src/main/res/drawable-hdpi/ic_chrome_reader_mode.png
Normal file
After Width: | Height: | Size: 216 B |
BIN
app/src/main/res/drawable-hdpi/ic_info_outline.png
Normal file
After Width: | Height: | Size: 551 B |
Before Width: | Height: | Size: 953 B |
BIN
app/src/main/res/drawable-hdpi/ic_open_in_browser.png
Normal file
After Width: | Height: | Size: 204 B |
BIN
app/src/main/res/drawable-hdpi/ic_settings.png
Normal file
After Width: | Height: | Size: 498 B |
Before Width: | Height: | Size: 434 B |
BIN
app/src/main/res/drawable-mdpi/ic_add.png
Normal file
After Width: | Height: | Size: 116 B |
Before Width: | Height: | Size: 86 B |
BIN
app/src/main/res/drawable-mdpi/ic_bug_report.png
Normal file
After Width: | Height: | Size: 212 B |
BIN
app/src/main/res/drawable-mdpi/ic_chrome_reader_mode.png
Normal file
After Width: | Height: | Size: 136 B |
BIN
app/src/main/res/drawable-mdpi/ic_info_outline.png
Normal file
After Width: | Height: | Size: 355 B |
Before Width: | Height: | Size: 655 B |
BIN
app/src/main/res/drawable-mdpi/ic_open_in_browser.png
Normal file
After Width: | Height: | Size: 157 B |
BIN
app/src/main/res/drawable-mdpi/ic_settings.png
Normal file
After Width: | Height: | Size: 339 B |
Before Width: | Height: | Size: 307 B |
BIN
app/src/main/res/drawable-xhdpi/ic_add.png
Normal file
After Width: | Height: | Size: 168 B |
Before Width: | Height: | Size: 108 B |
BIN
app/src/main/res/drawable-xhdpi/ic_bug_report.png
Normal file
After Width: | Height: | Size: 312 B |
BIN
app/src/main/res/drawable-xhdpi/ic_chrome_reader_mode.png
Normal file
After Width: | Height: | Size: 171 B |
BIN
app/src/main/res/drawable-xhdpi/ic_info_outline.png
Normal file
After Width: | Height: | Size: 725 B |
Before Width: | Height: | Size: 1.2 KiB |
BIN
app/src/main/res/drawable-xhdpi/ic_open_in_browser.png
Normal file
After Width: | Height: | Size: 230 B |
BIN
app/src/main/res/drawable-xhdpi/ic_settings.png
Normal file
After Width: | Height: | Size: 606 B |
Before Width: | Height: | Size: 542 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_add.png
Normal file
After Width: | Height: | Size: 242 B |
Before Width: | Height: | Size: 114 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_bug_report.png
Normal file
After Width: | Height: | Size: 466 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_chrome_reader_mode.png
Normal file
After Width: | Height: | Size: 263 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_info_outline.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 1.9 KiB |
BIN
app/src/main/res/drawable-xxhdpi/ic_open_in_browser.png
Normal file
After Width: | Height: | Size: 318 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_settings.png
Normal file
After Width: | Height: | Size: 907 B |
Before Width: | Height: | Size: 768 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_add.png
Normal file
After Width: | Height: | Size: 500 B |
Before Width: | Height: | Size: 119 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_bug_report.png
Normal file
After Width: | Height: | Size: 573 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_chrome_reader_mode.png
Normal file
After Width: | Height: | Size: 296 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_info_outline.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 2.6 KiB |
BIN
app/src/main/res/drawable-xxxhdpi/ic_open_in_browser.png
Normal file
After Width: | Height: | Size: 408 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_settings.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.1 KiB |
@ -2,12 +2,12 @@
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item
|
||||
android:drawable="@color/white"/>
|
||||
android:drawable="@color/ic_launcher_background"/>
|
||||
|
||||
<item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/ic_launcher"/>
|
||||
android:src="@mipmap/ic_launcher_foreground"/>
|
||||
</item>
|
||||
|
||||
</layer-list>
|
BIN
app/src/main/res/drawable/bg.png
Normal file
After Width: | Height: | Size: 406 B |
4
app/src/main/res/drawable/ic_info_outline_white_48px.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<vector android:height="24dp" android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FFFFFF" android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/>
|
||||
</vector>
|
4
app/src/main/res/drawable/ic_thumb_up_white_48px.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<vector android:height="24dp" android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FFFFFF" android:pathData="M1,21h4L5,9L1,9v12zM23,10c0,-1.1 -0.9,-2 -2,-2h-6.31l0.95,-4.57 0.03,-0.32c0,-0.41 -0.17,-0.79 -0.44,-1.06L14.17,1 7.59,7.59C7.22,7.95 7,8.45 7,9v10c0,1.1 0.9,2 2,2h9c0.83,0 1.54,-0.5 1.84,-1.22l3.02,-7.05c0.09,-0.23 0.14,-0.47 0.14,-0.73v-1.91l-0.01,-0.01L23,10z"/>
|
||||
</vector>
|
BIN
app/src/main/res/drawable/web_hi_res_512.png
Normal file
After Width: | Height: | Size: 20 KiB |
@ -15,7 +15,8 @@
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:theme="@style/ToolBarStyle" />
|
||||
app:theme="@style/ToolBarStyle"
|
||||
app:popupTheme="?attr/toolbarPopupTheme" />
|
||||
|
||||
</android.support.design.widget.AppBarLayout>
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<com.github.stkent.amplify.prompt.DefaultLayoutPromptView
|
||||
android:id="@+id/prompt_view"
|
||||
android:id="@+id/promptView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:prompt_view_user_opinion_question_title="@string/rating_prompt_title"
|
||||
@ -34,7 +34,7 @@
|
||||
android:id="@+id/coordLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@id/prompt_view">
|
||||
android:layout_below="@id/promptView">
|
||||
|
||||
<android.support.design.widget.CoordinatorLayout
|
||||
android:id="@+id/intern_coordLayout"
|
||||
@ -51,10 +51,11 @@
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:id="@+id/toolBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:theme="@style/ToolBarStyle" />
|
||||
app:theme="@style/ToolBarStyle"
|
||||
app:popupTheme="?attr/toolbarPopupTheme" />
|
||||
|
||||
</android.support.design.widget.AppBarLayout>
|
||||
|
||||
@ -88,7 +89,7 @@
|
||||
android:background="@color/transparent"
|
||||
android:visibility="gone" />
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/my_recycler_view"
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/transparent"
|
||||
|
@ -14,7 +14,8 @@
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:theme="@style/ToolBarStyle" />
|
||||
app:theme="@style/ToolBarStyle"
|
||||
app:popupTheme="?attr/toolbarPopupTheme" />
|
||||
|
||||
</android.support.design.widget.AppBarLayout>
|
||||
<LinearLayout
|
||||
@ -27,7 +28,7 @@
|
||||
android:paddingTop="@dimen/activity_vertical_margin">
|
||||
<!-- Login progress -->
|
||||
<ProgressBar
|
||||
android:id="@+id/login_progress"
|
||||
android:id="@+id/loginProgress"
|
||||
style="?android:attr/progressBarStyleLarge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@ -35,7 +36,7 @@
|
||||
android:visibility="gone"/>
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/login_form"
|
||||
android:id="@+id/loginForm"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
@ -51,15 +52,13 @@
|
||||
>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/url"
|
||||
android:id="@+id/urlView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/prompt_url"
|
||||
android:imeActionId="@+id/login"
|
||||
android:inputType="textUri"
|
||||
android:imeOptions="actionUnspecified"
|
||||
android:maxLines="1"
|
||||
/>
|
||||
android:inputType="textUri"
|
||||
android:maxLines="1" />
|
||||
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
@ -77,13 +76,12 @@
|
||||
android:visibility="gone">
|
||||
|
||||
<AutoCompleteTextView
|
||||
android:id="@+id/login"
|
||||
android:id="@+id/loginView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/prompt_login"
|
||||
android:inputType="text"
|
||||
android:maxLines="1"
|
||||
/>
|
||||
android:maxLines="1" />
|
||||
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
@ -94,13 +92,12 @@
|
||||
android:visibility="gone">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/password"
|
||||
android:id="@+id/passwordView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/prompt_password"
|
||||
android:inputType="textPassword"
|
||||
android:maxLines="1"
|
||||
/>
|
||||
android:maxLines="1" />
|
||||
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
@ -118,7 +115,7 @@
|
||||
android:visibility="gone">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/httpLogin"
|
||||
android:id="@+id/httpLoginView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/prompt_http_login" />
|
||||
@ -131,15 +128,29 @@
|
||||
android:visibility="gone">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/httpPassword"
|
||||
android:id="@+id/httpPasswordView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textPassword"
|
||||
android:hint="@string/prompt_http_password" />
|
||||
android:hint="@string/prompt_http_password"
|
||||
android:inputType="textPassword" />
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
<Switch
|
||||
android:id="@+id/withSelfhostedCert"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/self_hosted_cert_switch" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/warningText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/self_signed_cert_warning"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
android:visibility="gone" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/email_sign_in_button"
|
||||
android:id="@+id/signInButton"
|
||||
style="?android:textAppearanceSmall"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -1,126 +1,148 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.constraint.ConstraintLayout
|
||||
<android.support.design.widget.CoordinatorLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="apps.amine.bou.readerforselfoss.ReaderActivity"
|
||||
android:background="?android:attr/windowBackground">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="200dp"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/source"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/imageView" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="6dp"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Headline"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/source" />
|
||||
|
||||
<org.sufficientlysecure.htmltextview.HtmlTextView
|
||||
android:id="@+id/content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:paddingBottom="48dp"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/title"
|
||||
tools:text="Some text @android:string/fingerprint_icon_content_description" />
|
||||
|
||||
<android.support.constraint.ConstraintLayout
|
||||
<android.support.v4.widget.NestedScrollView
|
||||
android:id="@+id/nestedScrollView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="35dp"
|
||||
android:layout_marginBottom="0dp"
|
||||
android:layout_marginEnd="0dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="#BBBBBB"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<android.support.constraint.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="200dp"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/source"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/imageView" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/titleView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="6dp"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Headline"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/source" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:paddingBottom="48dp"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/titleView"
|
||||
android:textColorLink="?attr/colorAccent"/>
|
||||
|
||||
<!--<org.sufficientlysecure.htmltextview.HtmlTextView
|
||||
android:id="@+id/content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:paddingBottom="48dp"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/titleView" />-->
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
|
||||
</android.support.v4.widget.NestedScrollView>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/content"
|
||||
app:layout_constraintVertical_bias="1.0">
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
android:layout_gravity="end|bottom|right">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/browserBtn"
|
||||
android:layout_width="35dp"
|
||||
android:layout_height="35dp"
|
||||
android:layout_centerVertical="true"
|
||||
<com.github.rubensousa.floatingtoolbar.FloatingToolbar
|
||||
android:id="@+id/floatingToolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:layout_gravity="bottom"
|
||||
app:floatingItemBackground="?attr/colorAccent"
|
||||
app:floatingMenu="@menu/reader_toolbar" />
|
||||
|
||||
<android.support.design.widget.FloatingActionButton
|
||||
android:id="@+id/fab"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_gravity="end|bottom|right"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_toLeftOf="@+id/shareBtn"
|
||||
android:layout_toStartOf="@+id/shareBtn"
|
||||
android:adjustViewBounds="true"
|
||||
android:background="@android:color/transparent"
|
||||
android:elevation="5dp"
|
||||
android:padding="4dp"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/ic_open_in_browser_black_24dp"
|
||||
android:tint="?android:attr/textColorPrimary"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/shareBtn"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:src="@drawable/ic_add"
|
||||
app:backgroundTint="?attr/colorAccent"
|
||||
app:fabSize="mini"
|
||||
app:rippleColor="?attr/colorAccentDark" />
|
||||
</FrameLayout>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/shareBtn"
|
||||
android:layout_width="35dp"
|
||||
android:layout_height="35dp"
|
||||
android:adjustViewBounds="true"
|
||||
android:background="@android:color/transparent"
|
||||
android:elevation="5dp"
|
||||
android:padding="4dp"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/ic_share_black_24dp"
|
||||
android:tint="?android:attr/textColorPrimary"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toEndOf="@+id/browserBtn"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
<FrameLayout
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone"
|
||||
android:animateLayoutChanges="true"
|
||||
android:alpha="0.8"
|
||||
android:background="@color/black"
|
||||
android:clickable="false">
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
<ProgressBar
|
||||
style="?android:attr/progressBarStyleLarge"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:progressTint="?attr/colorAccent" />
|
||||
</FrameLayout>
|
||||
|
||||
</android.support.design.widget.CoordinatorLayout>
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
|