Compare commits
106 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
04402c5ab9 | |||
f8f710df99 | |||
b8105bb6fb | |||
1d18c898b2 | |||
95e208000f | |||
ecdddef81d | |||
c9b1d329e6 | |||
e68c16c7a4 | |||
585c57fe3a | |||
d04cbac79c | |||
044585ee9b | |||
299478e840 | |||
b2d69be5f8 | |||
dc970bbf3c | |||
8717bd5d5d | |||
5b307a8407 | |||
daef66087d | |||
1ad1cf4460 | |||
c0b9718368 | |||
d684f323b8 | |||
24a1c56fe6 | |||
cdeba4f84e | |||
cafba196cf | |||
493b1b12b3 | |||
5320f88230 | |||
246ec2c3ac | |||
9c9b45aeab | |||
8c5dc43735 | |||
b1e812314f | |||
c14f47a74b | |||
58a5b4a5e5 | |||
1cfc2bf36f | |||
5a56d826d9 | |||
8ad8b55424 | |||
3da1d431db | |||
4565079f29 | |||
3482092cb2 | |||
2df5e52de0 | |||
0ef4fc67fa | |||
e2bfd549d3 | |||
7071af5fa5 | |||
95f267f701 | |||
f363bbcc0c | |||
65a912f271 | |||
758661f5fb | |||
2d3c297726 | |||
ca85e3d3ed | |||
2190ad0387 | |||
0d067e05af | |||
c3305b7523 | |||
fb84b31122 | |||
9d40026ef7 | |||
041a225992 | |||
b42dc7f87c | |||
0283e49c27 | |||
e5e1b2f5a5 | |||
813a0ae475 | |||
f0e036cdd8 |
75
.github/CONTRIBUTING.md
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
# Introduction
|
||||
|
||||
### Hey you !
|
||||
|
||||
Thank you for wanting to help. Even the smallest things can help this project become better.
|
||||
|
||||
Please read the guidelines before contributing, and follow them (or try to) when contributing.
|
||||
|
||||
### What you can do to help.
|
||||
|
||||
There are many ways to contribute to this project, you could report bugs, request missing features, suggest enhancements and changes to existing ones. You also can improve the README with useful tips that could help the other users.
|
||||
|
||||
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)
|
||||
|
||||
### What I can't help you with.
|
||||
|
||||
Please, don't use the issue tracker for anything related to [Selfoss itself](https://github.com/SSilence/selfoss). The app calls the api provided by Selfoss, and can't help with solving issues with your Selfoss instance.
|
||||
|
||||
Always check if the web version of your instance is working.
|
||||
|
||||
# Some rules
|
||||
### Bug reports/Feature request
|
||||
|
||||
* Always search before reporting an issue or asking for a feature to avoid duplicates.
|
||||
* 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
|
||||
|
||||
* 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.
|
||||
* 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.
|
||||
* Always reference the issue you are working on in your PR description.
|
||||
* 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:
|
||||
|
||||
- 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 the following some parameters either in `~/.gradle/gradle.properties` or as gradle parameters (see the examples)
|
||||
|
||||
- mercuryApiKey: A [Mercury](https://mercury.postlight.com/web-parser/) web parser api key for the internal browser
|
||||
- feedbackEmail: An email to receive users feedback.
|
||||
- sourceUrl: an url to the source code, used in the settings
|
||||
- trackerUrl: an url to the tracker, used in the settings
|
||||
|
||||
### Examples:
|
||||
#### Inside ~/.gradle/gradle.properties
|
||||
|
||||
```
|
||||
appLoginUrl="URL"
|
||||
appLoginUsername="LOGIN"
|
||||
appLoginPassword="PASS"
|
||||
mercuryApiKey="LONGAPIKEY"
|
||||
feedbackEmail="EMAIL"
|
||||
sourceUrl="URLSOURCE"
|
||||
trackerUrl="URLTRACKER"
|
||||
```
|
||||
|
||||
#### 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"
|
||||
```
|
32
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
### Prerequisites
|
||||
|
||||
* [ ] Are you running the latest version?
|
||||
* [ ] Did you check for an existing issue ?
|
||||
* [ ] Are you reporting to the correct repository?
|
||||
* [ ] Did you perform a cursory search?
|
||||
* [ ] Did you read the `CONTRIBUTING` guide ?
|
||||
|
||||
### Description
|
||||
|
||||
[Description of the bug or feature]
|
||||
|
||||
### Steps to Reproduce
|
||||
|
||||
1. [First Step]
|
||||
2. [Second Step]
|
||||
3. [and so on...]
|
||||
|
||||
**Expected behavior:** [What you expected to happen]
|
||||
|
||||
**Actual behavior:** [What actually happened]
|
||||
|
||||
|
||||
### Screenshots (optional)
|
||||
|
||||
`...`
|
||||
|
||||
### Device
|
||||
|
||||
- Device (manufacturer, model ...)
|
||||
- OS (Android Version, ROM/Stock, Rooted/not, mods...)
|
||||
- App version _(See Prerequisites)_
|
13
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
## Types of changes
|
||||
|
||||
- [ ] I have read the **CONTRIBUTING** document.
|
||||
- [ ] My code follows the code style of this project.
|
||||
- [ ] I have updated the documentation accordingly.
|
||||
- [ ] I have added tests to cover my changes.
|
||||
- [ ] All new and existing tests passed.
|
||||
|
||||
This closes issue #XXX
|
||||
|
||||
This is implements feature #YYY
|
||||
|
||||
This finishes chore #ZZZ
|
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
|
144
CHANGELOG.md
@ -1,3 +1,147 @@
|
||||
**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.
|
||||
|
||||
**1.5.1.3/4**
|
||||
|
||||
- Fixes introduces by the previous alpha (1.5.1.2)
|
||||
|
||||
**1.5.1.2**
|
||||
|
||||
- Added testing to the CI.
|
||||
|
||||
- Code cleaning
|
||||
|
||||
- Display the pull to refresh loader on api call
|
||||
|
||||
- Fixes :
|
||||
|
||||
- Can't pull down to refresh on first launch
|
||||
|
||||
- Recurring crash because of the url
|
||||
|
||||
- Couldn't open some urls because of missing "http"
|
||||
|
||||
- Adding a source with invalid url would crash
|
||||
|
||||
|
||||
**1.5.1.1**
|
||||
|
||||
- Fixed an issue when trying to add a source without being logged in.
|
||||
|
||||
- Reloading drawer tags badges on slide to refresh.
|
||||
|
||||
**1.5.1**
|
||||
|
||||
- Added a drawer for filtering sources and tags.
|
||||
|
||||
- You can now search for items from the toolbar.
|
||||
|
||||
**1.5.0.2**
|
||||
|
||||
- If the content in the article viewer is empty, the article will open in a custom tab.
|
||||
|
||||
- Added a share button, and an "open in browser" button to the bottom of the article viewer.
|
||||
|
||||
- Updated custom tab code.
|
||||
|
||||
**1.5.0.1**
|
||||
|
||||
- The release APK wasn't working at all.
|
||||
|
23
README.md
@ -1,29 +1,24 @@
|
||||
# ReaderForSelfoss
|
||||
|
||||
[](https://crowdin.com/project/readerforselfoss) [](https://gitter.im/amine-bou/ReaderForSelfoss)
|
||||
|
||||
[](http://jenkins.amine-bou.fr/job/ReaderForSelfoss/)
|
||||
|
||||
[](https://codebeat.co/projects/github-com-aminecmi-readerforselfoss-master) [](https://www.codetriage.com/aminecmi/readerforselfoss)
|
||||
|
||||
This is the repo of [Reader For Selfoss](https://play.google.com/store/apps/details?id=apps.amine.bou.readerforselfoss&hl=en).
|
||||
|
||||
It's an RSS Reader for Android, that **only** works with [Selfoss](https://selfoss.aditu.de/)
|
||||
|
||||
The last APK built from source is available [here](https://jenkins.amine-bou.fr/job/ReaderForSelfoss/lastSuccessfulBuild/artifact/SignApksBuilder-out/selfoss-key/selfoss/app-githubConfig-release-unsigned.apk/app-githubConfig-release.apk).
|
||||
|
||||
## Build
|
||||
|
||||
You can directly import this project into IntellIJ/Android Studio.
|
||||
## Want to help ?
|
||||
|
||||
You'll have to:
|
||||
|
||||
- [Create your own launcher icon](https://developer.android.com/studio/write/image-asset-studio.html#creating-launcher)
|
||||
|
||||
- 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`
|
||||
|
||||
- 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
|
||||
Check the [Contribution guide](https://github.com/aminecmi/ReaderforSelfoss/blob/master/.github/CONTRIBUTING.md)
|
||||
|
||||
## Useful links
|
||||
|
||||
- [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 me translate the app](https://poeditor.com/join/project/viHr8ujJ7S)
|
137
app/build.gradle
@ -8,6 +8,21 @@ 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: 'com.android.application'
|
||||
|
||||
apply plugin: 'io.fabric'
|
||||
@ -19,14 +34,14 @@ repositories {
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 25
|
||||
buildToolsVersion "25.0.3"
|
||||
compileSdkVersion 26
|
||||
buildToolsVersion "26.0.1"
|
||||
defaultConfig {
|
||||
applicationId "apps.amine.bou.readerforselfoss"
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 25
|
||||
versionCode 1501
|
||||
versionName "1.5.0.1"
|
||||
targetSdkVersion 26
|
||||
versionCode versionCodeFromGit()
|
||||
versionName versionNameFromGit()
|
||||
|
||||
// Enabling multidex support.
|
||||
multiDexEnabled true
|
||||
@ -35,13 +50,28 @@ android {
|
||||
disable 'InvalidPackage'
|
||||
}
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
|
||||
// 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
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'),
|
||||
'proguard-rules.pro'
|
||||
}
|
||||
debug {
|
||||
buildConfigField "String", "LOGIN_URL", appLoginUrl
|
||||
buildConfigField "String", "LOGIN_USERNAME", appLoginUsername
|
||||
buildConfigField "String", "LOGIN_PASSWORD", appLoginPassword
|
||||
}
|
||||
}
|
||||
flavorDimensions "build"
|
||||
productFlavors {
|
||||
@ -59,66 +89,123 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Testing
|
||||
androidTestCompile 'com.android.support.test.espresso:espresso-core:3.0.0'
|
||||
androidTestCompile 'com.android.support.test:runner:1.0.0'
|
||||
// Espresso-contrib for DatePicker, RecyclerView, Drawer actions, Accessibility checks, CountingIdlingResource
|
||||
androidTestCompile 'com.android.support.test.espresso:espresso-contrib:3.0.0'
|
||||
// Espresso-intents for validation and stubbing of Intents
|
||||
androidTestCompile 'com.android.support.test.espresso:espresso-intents:3.0.0'
|
||||
|
||||
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
|
||||
|
||||
// Android Support
|
||||
compile 'com.android.support:appcompat-v7:25.3.1'
|
||||
compile 'com.android.support:design:25.3.1'
|
||||
compile 'com.android.support:recyclerview-v7:25.3.1'
|
||||
compile 'com.android.support:support-v4:25.3.1'
|
||||
compile 'com.android.support:support-vector-drawable:25.3.1'
|
||||
compile 'com.android.support:customtabs:25.3.1'
|
||||
compile 'com.android.support:cardview-v7:25.3.1'
|
||||
compile 'com.android.support:appcompat-v7:26.0.1'
|
||||
compile 'com.android.support:design:26.0.1'
|
||||
compile 'com.android.support:recyclerview-v7:26.0.1'
|
||||
compile 'com.android.support:support-v4:26.0.1'
|
||||
compile 'com.android.support:support-vector-drawable:26.0.1'
|
||||
compile 'com.android.support:customtabs:26.0.1'
|
||||
compile 'com.android.support:cardview-v7:26.0.1'
|
||||
compile 'com.android.support.constraint:constraint-layout:1.0.2'
|
||||
|
||||
// Firebase + crashlytics
|
||||
compile 'com.google.firebase:firebase-core:10.2.6'
|
||||
compile 'com.google.firebase:firebase-config:10.2.6'
|
||||
compile 'com.google.firebase:firebase-invites:10.2.6'
|
||||
compile 'com.google.firebase:firebase-core:11.2.0'
|
||||
compile 'com.google.firebase:firebase-config:11.2.0'
|
||||
compile 'com.google.firebase:firebase-invites:11.2.0'
|
||||
compile('com.crashlytics.sdk.android:crashlytics:2.6.8@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:5.9.7@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.roughike:bottom-bar:2.2.0'
|
||||
compile 'com.ashokvarma.android:bottom-navigation-bar:2.0.2'
|
||||
compile 'com.melnykov:floatingactionbutton:1.3.0'
|
||||
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'
|
||||
|
||||
// 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.0'
|
||||
compile 'com.klinkerapps:drag-dismiss-activity:1.4.3'
|
||||
|
||||
// Drawer
|
||||
compile('com.mikepenz:materialdrawer:5.9.5@aar') {
|
||||
transitive = true
|
||||
}
|
||||
compile 'com.anupcowkur:reservoir:3.1.0'
|
||||
|
||||
// Themes
|
||||
compile 'com.52inc:scoops:1.0.0'
|
||||
|
||||
}
|
||||
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
|
||||
|
||||
afterEvaluate {
|
||||
initFabricPropertiesIfNeeded()
|
||||
initAppLoginPropertiesIfNeeded()
|
||||
initAppForSecretPropertiesIfNeeded()
|
||||
}
|
||||
|
||||
def initFabricPropertiesIfNeeded() {
|
||||
def propertiesFile = file('fabric.properties')
|
||||
if (!propertiesFile.exists()) {
|
||||
def commentMessage = "This is autogenerated fabric property from system environment to prevent key to be committed to source control."
|
||||
ant.propertyfile(file: "fabric.properties", comment: commentMessage) {
|
||||
entry(key: "apiSecret", value: crashlyticsdemoApisecret)
|
||||
entry(key: "apiKey", value: crashlyticsdemoApikey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def initAppLoginPropertiesIfNeeded() {
|
||||
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: "appLoginUrl", value: System.getProperty("appLoginUrl"))
|
||||
entry(key: "appLoginUsername", value: System.getProperty("appLoginUsername"))
|
||||
entry(key: "appLoginPassword", value: System.getProperty("appLoginPassword"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
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"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
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.**
|
@ -0,0 +1,4 @@
|
||||
package apps.amine.bou.readerforselfoss
|
||||
|
||||
|
||||
// TODO: test source adding
|
@ -0,0 +1,123 @@
|
||||
package apps.amine.bou.readerforselfoss
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.support.test.InstrumentationRegistry
|
||||
import android.support.test.espresso.Espresso.onView
|
||||
import android.support.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
|
||||
import android.support.test.espresso.action.ViewActions.*
|
||||
import android.support.test.espresso.assertion.ViewAssertions.matches
|
||||
import android.support.test.espresso.contrib.DrawerActions
|
||||
import android.support.test.espresso.intent.Intents
|
||||
import android.support.test.espresso.intent.Intents.intended
|
||||
import android.support.test.espresso.intent.Intents.times
|
||||
import android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent
|
||||
import android.support.test.espresso.matcher.ViewMatchers.*
|
||||
import android.support.test.rule.ActivityTestRule
|
||||
import android.support.test.runner.AndroidJUnit4
|
||||
import android.view.KeyEvent
|
||||
|
||||
import com.mikepenz.aboutlibraries.ui.LibsActivity
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import apps.amine.bou.readerforselfoss.settings.SettingsActivity
|
||||
import apps.amine.bou.readerforselfoss.utils.Config
|
||||
import org.junit.After
|
||||
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class HomeActivityEspressoTest {
|
||||
lateinit var context: Context
|
||||
|
||||
@Rule @JvmField
|
||||
val rule = ActivityTestRule(HomeActivity::class.java, true, false)
|
||||
|
||||
@Before
|
||||
fun clearData() {
|
||||
context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
|
||||
val editor =
|
||||
context
|
||||
.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
|
||||
.edit()
|
||||
editor.clear()
|
||||
|
||||
editor.putString("url", BuildConfig.LOGIN_URL)
|
||||
editor.putString("login", BuildConfig.LOGIN_USERNAME)
|
||||
editor.putString("password", BuildConfig.LOGIN_PASSWORD)
|
||||
|
||||
editor.commit()
|
||||
|
||||
Intents.init()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun menuItems() {
|
||||
|
||||
rule.launchActivity(Intent())
|
||||
|
||||
onView(
|
||||
withMenu(
|
||||
id = R.id.action_search,
|
||||
titleId = R.string.menu_home_search
|
||||
)
|
||||
).perform(click())
|
||||
|
||||
onView(withId(R.id.search_bar)).check(matches(isDisplayed()))
|
||||
|
||||
onView(withId(R.id.search_src_text)).perform(typeText("android"), pressKey(KeyEvent.KEYCODE_SEARCH), closeSoftKeyboard())
|
||||
|
||||
onView(withContentDescription(R.string.abc_toolbar_collapse_description)).perform(click())
|
||||
|
||||
|
||||
onView(withMenu(id = R.id.readAll, titleId = R.string.readAll)).perform(click())
|
||||
|
||||
openActionBarOverflowOrOptionsMenu(context)
|
||||
|
||||
onView(withMenu(id = R.id.refresh, titleId = R.string.menu_home_refresh))
|
||||
.perform(click())
|
||||
|
||||
openActionBarOverflowOrOptionsMenu(context)
|
||||
|
||||
onView(withText(R.string.action_disconnect)).perform(click())
|
||||
|
||||
intended(hasComponent(LoginActivity::class.java.name), times(1))
|
||||
|
||||
//onView(isRoot()).perform(pressBack())
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun drawerTesting() {
|
||||
|
||||
rule.launchActivity(Intent())
|
||||
|
||||
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(isRoot()).perform(pressBack())
|
||||
intended(hasComponent(HomeActivity::class.java.name))
|
||||
|
||||
onView(withId(R.id.material_drawer_layout)).perform(DrawerActions.open())
|
||||
|
||||
onView(withId(R.id.material_drawer_layout)).perform(DrawerActions.open())
|
||||
onView(withText(R.string.drawer_action_clear)).perform(click())
|
||||
|
||||
// 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
|
||||
|
||||
@After
|
||||
fun releaseIntents() {
|
||||
Intents.release()
|
||||
}
|
||||
}
|
@ -0,0 +1,121 @@
|
||||
package apps.amine.bou.readerforselfoss
|
||||
|
||||
import java.util.*
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.support.test.InstrumentationRegistry.getInstrumentation
|
||||
import android.support.test.espresso.Espresso.onView
|
||||
import android.support.test.espresso.action.ViewActions.click
|
||||
import android.support.test.espresso.assertion.ViewAssertions.matches
|
||||
import android.support.test.espresso.intent.Intents
|
||||
import android.support.test.espresso.intent.Intents.intended
|
||||
import android.support.test.espresso.intent.Intents.times
|
||||
import android.support.test.espresso.intent.matcher.IntentMatchers.*
|
||||
import android.support.test.espresso.intent.matcher.UriMatchers.hasHost
|
||||
import android.support.test.espresso.matcher.ViewMatchers.*
|
||||
import android.support.test.rule.ActivityTestRule
|
||||
import android.support.test.runner.AndroidJUnit4
|
||||
|
||||
import org.hamcrest.Matchers.allOf
|
||||
import org.hamcrest.Matchers.equalTo
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import apps.amine.bou.readerforselfoss.utils.Config
|
||||
import org.junit.After
|
||||
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class IntroActivityEspressoTest {
|
||||
|
||||
@Rule @JvmField
|
||||
val rule = ActivityTestRule(IntroActivity::class.java, true, false)
|
||||
|
||||
@Before
|
||||
fun clearData() {
|
||||
val editor =
|
||||
getInstrumentation().targetContext
|
||||
.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
|
||||
.edit()
|
||||
editor.clear()
|
||||
editor.commit()
|
||||
|
||||
Intents.init()
|
||||
}
|
||||
|
||||
/*@Test
|
||||
fun nextEachTimes() {
|
||||
|
||||
rule.launchActivity(Intent())
|
||||
|
||||
onView(withText(R.string.intro_hello_title)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.button_next)).perform(click())
|
||||
onView(withText(R.string.intro_needs_selfoss_message)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.button_next)).perform(click())
|
||||
onView(withText(R.string.intro_all_set_message)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.button_next)).perform(click())
|
||||
|
||||
intended(hasComponent(IntroActivity::class.java.name), times(1))
|
||||
intended(hasComponent(LoginActivity::class.java.name), times(1))
|
||||
|
||||
}*/
|
||||
|
||||
@Test
|
||||
fun nextBackRandomTimes() {
|
||||
val max = 5
|
||||
val min = 1
|
||||
|
||||
val random = (Random().nextInt(max + 1 - min)) + min
|
||||
|
||||
rule.launchActivity(Intent())
|
||||
|
||||
onView(withText(R.string.intro_hello_title)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.button_next)).perform(click())
|
||||
|
||||
repeat(random) {_ ->
|
||||
onView(withText(R.string.intro_needs_selfoss_message)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.button_next)).perform(click())
|
||||
onView(withText(R.string.intro_all_set_message)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.button_back)).perform(click())
|
||||
}
|
||||
|
||||
onView(withId(R.id.button_next)).perform(click())
|
||||
onView(withText(R.string.intro_all_set_message)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.button_next)).perform(click())
|
||||
|
||||
intended(hasComponent(IntroActivity::class.java.name), times(1))
|
||||
intended(hasComponent(LoginActivity::class.java.name), times(1))
|
||||
|
||||
}
|
||||
|
||||
@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()
|
||||
}
|
||||
}
|
@ -0,0 +1,164 @@
|
||||
package apps.amine.bou.readerforselfoss
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.support.test.InstrumentationRegistry
|
||||
import android.support.test.espresso.Espresso.onView
|
||||
import android.support.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
|
||||
import android.support.test.espresso.action.ViewActions.*
|
||||
import android.support.test.espresso.assertion.ViewAssertions.matches
|
||||
import android.support.test.espresso.intent.Intents
|
||||
import android.support.test.espresso.intent.Intents.intended
|
||||
import android.support.test.espresso.intent.Intents.times
|
||||
import android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent
|
||||
import android.support.test.espresso.matcher.ViewMatchers
|
||||
import android.support.test.espresso.matcher.ViewMatchers.*
|
||||
import android.support.test.rule.ActivityTestRule
|
||||
import android.support.test.runner.AndroidJUnit4
|
||||
|
||||
import com.mikepenz.aboutlibraries.ui.LibsActivity
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import apps.amine.bou.readerforselfoss.utils.Config
|
||||
import org.junit.After
|
||||
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class LoginActivityEspressoTest {
|
||||
|
||||
@Rule @JvmField
|
||||
val rule = ActivityTestRule(LoginActivity::class.java, true, false)
|
||||
|
||||
lateinit var context: Context
|
||||
lateinit var url: String
|
||||
lateinit var username: String
|
||||
lateinit var password: String
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
val editor =
|
||||
context
|
||||
.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
|
||||
.edit()
|
||||
editor.clear()
|
||||
editor.commit()
|
||||
|
||||
|
||||
url = BuildConfig.LOGIN_URL
|
||||
username = BuildConfig.LOGIN_USERNAME
|
||||
password = BuildConfig.LOGIN_PASSWORD
|
||||
|
||||
Intents.init()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun menuItems() {
|
||||
|
||||
rule.launchActivity(Intent())
|
||||
|
||||
openActionBarOverflowOrOptionsMenu(context)
|
||||
|
||||
onView(withText(R.string.action_about)).perform(click())
|
||||
|
||||
intended(hasComponent(LibsActivity::class.java.name), times(1))
|
||||
|
||||
onView(isRoot()).perform(pressBack())
|
||||
|
||||
intended(hasComponent(LoginActivity::class.java.name))
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun wrongLoginUrl() {
|
||||
rule.launchActivity(Intent())
|
||||
|
||||
onView(withId(R.id.login_progress))
|
||||
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)))
|
||||
|
||||
onView(withId(R.id.url)).perform(click()).perform(typeText("WRONGURL"))
|
||||
|
||||
onView(withId(R.id.email_sign_in_button)).perform(click())
|
||||
|
||||
onView(withId(R.id.urlLayout)).check(matches(isHintOrErrorEnabled()))
|
||||
}
|
||||
|
||||
// TODO: Add tests for multiple false urls with dialog
|
||||
|
||||
@Test
|
||||
fun emptyAuthData() {
|
||||
|
||||
rule.launchActivity(Intent())
|
||||
|
||||
onView(withId(R.id.url)).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.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.passwordLayout)).check(matches(isHintOrErrorEnabled()))
|
||||
|
||||
onView(withId(R.id.email_sign_in_button)).perform(click())
|
||||
|
||||
onView(withId(R.id.passwordLayout)).check(
|
||||
matches(
|
||||
isHintOrErrorEnabled())
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun wrongAuthData() {
|
||||
|
||||
rule.launchActivity(Intent())
|
||||
|
||||
onView(withId(R.id.url)).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.password)).perform(click()).perform(typeText("WRONGPASS"), closeSoftKeyboard())
|
||||
|
||||
onView(withId(R.id.email_sign_in_button)).perform(click())
|
||||
|
||||
onView(withId(R.id.urlLayout)).check(matches(isHintOrErrorEnabled()))
|
||||
onView(withId(R.id.loginLayout)).check(matches(isHintOrErrorEnabled()))
|
||||
onView(withId(R.id.passwordLayout)).check(matches(isHintOrErrorEnabled()))
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun workingAuth() {
|
||||
|
||||
rule.launchActivity(Intent())
|
||||
|
||||
onView(withId(R.id.url)).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.password)).perform(click()).perform(typeText(password), closeSoftKeyboard())
|
||||
|
||||
onView(withId(R.id.email_sign_in_button)).perform(click())
|
||||
|
||||
intended(hasComponent(HomeActivity::class.java.name))
|
||||
|
||||
}
|
||||
|
||||
@After
|
||||
fun releaseIntents() {
|
||||
Intents.release()
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
package apps.amine.bou.readerforselfoss
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.preference.PreferenceManager
|
||||
import android.support.test.InstrumentationRegistry.getInstrumentation
|
||||
import android.support.test.espresso.intent.Intents
|
||||
import android.support.test.espresso.intent.Intents.intended
|
||||
import android.support.test.espresso.intent.Intents.times
|
||||
import android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent
|
||||
import android.support.test.rule.ActivityTestRule
|
||||
import android.support.test.runner.AndroidJUnit4
|
||||
import org.junit.After
|
||||
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class MainActivityEspressoTest {
|
||||
|
||||
lateinit var intent: Intent
|
||||
lateinit var preferencesEditor: SharedPreferences.Editor
|
||||
|
||||
@Rule @JvmField
|
||||
val rule = ActivityTestRule(MainActivity::class.java, true, false)
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
intent = Intent()
|
||||
val context = getInstrumentation().targetContext
|
||||
|
||||
// create a SharedPreferences editor
|
||||
preferencesEditor = PreferenceManager.getDefaultSharedPreferences(context).edit()
|
||||
|
||||
Intents.init()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkFirstOpenLaunchesIntro() {
|
||||
preferencesEditor.putBoolean("firstStart", true)
|
||||
preferencesEditor.commit()
|
||||
|
||||
rule.launchActivity(intent)
|
||||
|
||||
intended(hasComponent(MainActivity::class.java.name))
|
||||
intended(hasComponent(IntroActivity::class.java.name))
|
||||
intended(hasComponent(LoginActivity::class.java.name), times(0))
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkNotFirstOpenLaunchesLogin() {
|
||||
preferencesEditor.putBoolean("firstStart", false)
|
||||
preferencesEditor.commit()
|
||||
|
||||
rule.launchActivity(intent)
|
||||
|
||||
intended(hasComponent(MainActivity::class.java.name))
|
||||
intended(hasComponent(LoginActivity::class.java.name))
|
||||
intended(hasComponent(IntroActivity::class.java.name), times(0))
|
||||
|
||||
}
|
||||
|
||||
@After
|
||||
fun releaseIntents() {
|
||||
Intents.release()
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package apps.amine.bou.readerforselfoss
|
||||
|
||||
import android.view.View
|
||||
import android.support.design.widget.TextInputLayout
|
||||
import android.support.test.espresso.matcher.ViewMatchers
|
||||
|
||||
import org.hamcrest.Description
|
||||
import org.hamcrest.Matcher
|
||||
import org.hamcrest.TypeSafeMatcher
|
||||
import org.hamcrest.Matchers
|
||||
|
||||
|
||||
|
||||
fun isHintOrErrorEnabled(): Matcher<View> =
|
||||
object : TypeSafeMatcher<View>() {
|
||||
override fun describeTo(description: Description?) {}
|
||||
|
||||
override fun matchesSafely(item: View?): Boolean {
|
||||
if (item !is TextInputLayout) {
|
||||
return false
|
||||
}
|
||||
|
||||
return item.isHintEnabled || item.isErrorEnabled
|
||||
}
|
||||
}
|
||||
|
||||
fun withMenu(id: Int, titleId: Int): Matcher<View> =
|
||||
Matchers.anyOf(
|
||||
ViewMatchers.withId(id),
|
||||
ViewMatchers.withText(titleId)
|
||||
)
|
@ -14,7 +14,7 @@
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
android:theme="@style/NoBar">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:theme="@style/SplashTheme">
|
||||
@ -64,6 +64,9 @@
|
||||
<activity android:name=".ReaderActivity"
|
||||
android:theme="@style/DragDismissTheme">
|
||||
</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,18 +2,23 @@ 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 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 retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
import apps.amine.bou.readerforselfoss.utils.isBaseUrlValid
|
||||
import com.ftinc.scoop.Scoop
|
||||
|
||||
|
||||
class AddSourceActivity : AppCompatActivity() {
|
||||
@ -22,17 +27,28 @@ class AddSourceActivity : AppCompatActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
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 = findViewById(R.id.progress) as ProgressBar
|
||||
val mForm = findViewById(R.id.formContainer) as ConstraintLayout
|
||||
val mNameInput = findViewById(R.id.nameInput) as EditText
|
||||
val mSourceUri = findViewById(R.id.sourceUri) as EditText
|
||||
val mTags = findViewById(R.id.tags) as EditText
|
||||
val mSpoutsSpinner = findViewById(R.id.spoutsSpinner) as Spinner
|
||||
val mSaveBtn = findViewById(R.id.saveBtn) as Button
|
||||
val api = SelfossApi(this)
|
||||
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 {
|
||||
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) {
|
||||
@ -40,7 +56,9 @@ class AddSourceActivity : AppCompatActivity() {
|
||||
mNameInput.setText(intent.getStringExtra(Intent.EXTRA_TITLE))
|
||||
}
|
||||
|
||||
mSaveBtn.setOnClickListener { handleSaveSource(mTags, mNameInput.text.toString(), mSourceUri.text.toString(), api) }
|
||||
mSaveBtn.setOnClickListener {
|
||||
handleSaveSource(mTags, mNameInput.text.toString(), mSourceUri.text.toString(), api!!)
|
||||
}
|
||||
|
||||
|
||||
val spoutsKV = HashMap<String, String>()
|
||||
@ -57,15 +75,12 @@ class AddSourceActivity : AppCompatActivity() {
|
||||
|
||||
val config = Config(this)
|
||||
|
||||
if (config.baseUrl.isEmpty() || !isUrlValid(config.baseUrl)) {
|
||||
Toast.makeText(this, getString(R.string.addStringNoUrl), Toast.LENGTH_SHORT).show()
|
||||
val i = Intent(this, LoginActivity::class.java)
|
||||
startActivity(i)
|
||||
finish()
|
||||
if (config.baseUrl.isEmpty() || !config.baseUrl.isBaseUrlValid()) {
|
||||
mustLoginToAddSource()
|
||||
} else {
|
||||
|
||||
var items: Map<String, Spout>
|
||||
api.spouts().enqueue(object : Callback<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()!!
|
||||
@ -78,7 +93,11 @@ class AddSourceActivity : AppCompatActivity() {
|
||||
mProgress.visibility = View.GONE
|
||||
mForm.visibility = View.VISIBLE
|
||||
|
||||
val spinnerArrayAdapter = ArrayAdapter(this@AddSourceActivity, android.R.layout.simple_spinner_item, itemsStrings)
|
||||
val spinnerArrayAdapter =
|
||||
ArrayAdapter(
|
||||
this@AddSourceActivity,
|
||||
android.R.layout.simple_spinner_item,
|
||||
itemsStrings)
|
||||
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
mSpoutsSpinner.adapter = spinnerArrayAdapter
|
||||
|
||||
@ -99,12 +118,25 @@ class AddSourceActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun mustLoginToAddSource() {
|
||||
Toast.makeText(this, getString(R.string.addStringNoUrl), Toast.LENGTH_SHORT).show()
|
||||
val i = Intent(this, LoginActivity::class.java)
|
||||
startActivity(i)
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun handleSaveSource(mTags: EditText, title: String, url: String, api: SelfossApi) {
|
||||
|
||||
if (title.isEmpty() || url.isEmpty() || mSpoutsValue == null || mSpoutsValue!!.isEmpty()) {
|
||||
Toast.makeText(this, R.string.form_not_complete, Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
api.createSource(title, url, mSpoutsValue!!, mTags.text.toString(), "").enqueue(object : Callback<SuccessResponse> {
|
||||
api.createSource(
|
||||
title,
|
||||
url,
|
||||
mSpoutsValue!!,
|
||||
mTags.text.toString(),
|
||||
""
|
||||
).enqueue(object : Callback<SuccessResponse> {
|
||||
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {
|
||||
if (response.body() != null && response.body()!!.isSuccess) {
|
||||
finish()
|
||||
|
@ -1,47 +1,51 @@
|
||||
package apps.amine.bou.readerforselfoss
|
||||
|
||||
import agency.tango.materialintroscreen.MaterialIntroActivity
|
||||
import agency.tango.materialintroscreen.MessageButtonBehaviour
|
||||
import agency.tango.materialintroscreen.SlideFragmentBuilder
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.preference.PreferenceManager
|
||||
import android.view.View
|
||||
|
||||
import agency.tango.materialintroscreen.MaterialIntroActivity
|
||||
import agency.tango.materialintroscreen.MessageButtonBehaviour
|
||||
import agency.tango.materialintroscreen.SlideFragmentBuilder
|
||||
import android.support.v7.app.AppCompatDelegate
|
||||
|
||||
|
||||
class IntroActivity : MaterialIntroActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
addSlide(SlideFragmentBuilder()
|
||||
.backgroundColor(R.color.colorPrimary)
|
||||
.buttonsColor(R.color.colorAccent)
|
||||
.image(R.mipmap.ic_launcher)
|
||||
.title(getString(R.string.intro_hello_title))
|
||||
.description(getString(R.string.intro_hello_message))
|
||||
.build())
|
||||
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
|
||||
|
||||
addSlide(SlideFragmentBuilder()
|
||||
.backgroundColor(R.color.colorAccent)
|
||||
.buttonsColor(R.color.colorPrimary)
|
||||
.image(R.drawable.ic_info_outline_white_48dp)
|
||||
.title(getString(R.string.intro_needs_selfoss_title))
|
||||
.description(getString(R.string.intro_needs_selfoss_message))
|
||||
.build(),
|
||||
MessageButtonBehaviour(View.OnClickListener {
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://selfoss.aditu.de"))
|
||||
startActivity(browserIntent)
|
||||
}, getString(R.string.intro_needs_selfoss_link)))
|
||||
.backgroundColor(R.color.colorPrimary)
|
||||
.buttonsColor(R.color.colorAccent)
|
||||
.image(R.drawable.web_hi_res_512)
|
||||
.title(getString(R.string.intro_hello_title))
|
||||
.description(getString(R.string.intro_hello_message))
|
||||
.build())
|
||||
|
||||
addSlide(SlideFragmentBuilder()
|
||||
.backgroundColor(R.color.colorPrimaryDark)
|
||||
.buttonsColor(R.color.colorAccentDark)
|
||||
.image(R.drawable.ic_thumb_up_white_48dp)
|
||||
.title(getString(R.string.intro_all_set_title))
|
||||
.description(getString(R.string.intro_all_set_message))
|
||||
.build())
|
||||
.backgroundColor(R.color.colorAccent)
|
||||
.buttonsColor(R.color.colorPrimary)
|
||||
.image(R.drawable.ic_info_outline_white_48px)
|
||||
.title(getString(R.string.intro_needs_selfoss_title))
|
||||
.description(getString(R.string.intro_needs_selfoss_message))
|
||||
.build(),
|
||||
MessageButtonBehaviour(View.OnClickListener {
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://selfoss.aditu.de"))
|
||||
startActivity(browserIntent)
|
||||
}, getString(R.string.intro_needs_selfoss_link)))
|
||||
|
||||
addSlide(SlideFragmentBuilder()
|
||||
.backgroundColor(R.color.colorPrimaryDark)
|
||||
.buttonsColor(R.color.colorAccentDark)
|
||||
.image(R.drawable.ic_thumb_up_white_48px)
|
||||
.title(getString(R.string.intro_all_set_title))
|
||||
.description(getString(R.string.intro_all_set_message))
|
||||
.build())
|
||||
}
|
||||
|
||||
override fun onFinish() {
|
||||
|
@ -9,20 +9,20 @@ import android.os.Bundle
|
||||
import android.support.design.widget.TextInputLayout
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.support.v7.widget.Toolbar
|
||||
import android.text.TextUtils
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.Button
|
||||
import android.widget.EditText
|
||||
import android.widget.Switch
|
||||
import android.widget.TextView
|
||||
import android.widget.*
|
||||
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 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
|
||||
@ -33,54 +33,85 @@ import retrofit2.Response
|
||||
|
||||
class LoginActivity : AppCompatActivity() {
|
||||
|
||||
private var settings: SharedPreferences? = null
|
||||
private var mProgressView: View? = null
|
||||
private var mUrlView: EditText? = null
|
||||
private var mLoginView: TextView? = null
|
||||
private var mHTTPLoginView: TextView? = null
|
||||
private var mPasswordView: EditText? = null
|
||||
private var mHTTPPasswordView: EditText? = null
|
||||
private var inValidCount: Int = 0
|
||||
private var isWithSelfSignedCert = false
|
||||
private var isWithLogin = false
|
||||
private var isWithHTTPLogin = false
|
||||
private var mLoginFormView: View? = null
|
||||
private var mFirebaseAnalytics: FirebaseAnalytics? = null
|
||||
|
||||
private lateinit var settings: SharedPreferences
|
||||
private lateinit var editor: SharedPreferences.Editor
|
||||
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 userIdentifier: String
|
||||
private var logErrors: Boolean = false
|
||||
|
||||
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
Scoop.getInstance().apply(this)
|
||||
setContentView(R.layout.activity_login)
|
||||
|
||||
settings = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
|
||||
if (settings!!.getString("url", "").isNotEmpty()) {
|
||||
goToMain()
|
||||
} else {
|
||||
checkAndDisplayStoreApk(this@LoginActivity)
|
||||
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()
|
||||
}
|
||||
|
||||
isWithLogin = false
|
||||
isWithHTTPLogin = false
|
||||
inValidCount = 0
|
||||
|
||||
settings = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
|
||||
userIdentifier = settings.getString("unique_id", "")
|
||||
logErrors = settings.getBoolean("loging_debug", false)
|
||||
|
||||
editor = settings.edit()
|
||||
|
||||
if (settings.getString("url", "").isNotEmpty()) {
|
||||
goToMain()
|
||||
} else {
|
||||
this@LoginActivity.checkAndDisplayStoreApk()
|
||||
}
|
||||
|
||||
mFirebaseAnalytics = FirebaseAnalytics.getInstance(this)
|
||||
mUrlView = findViewById(R.id.url) as EditText
|
||||
mLoginView = findViewById(R.id.login) as TextView
|
||||
mHTTPLoginView = findViewById(R.id.httpLogin) as TextView
|
||||
mPasswordView = findViewById(R.id.password) as EditText
|
||||
mHTTPPasswordView = findViewById(R.id.httpPassword) as EditText
|
||||
mUrlView = findViewById(R.id.url)
|
||||
mLoginView = findViewById(R.id.login)
|
||||
mHTTPLoginView = findViewById(R.id.httpLogin)
|
||||
mPasswordView = findViewById(R.id.password)
|
||||
mHTTPPasswordView = findViewById(R.id.httpPassword)
|
||||
mLoginFormView = findViewById(R.id.login_form)
|
||||
mProgressView = findViewById(R.id.login_progress)
|
||||
|
||||
val mSwitch = findViewById(R.id.withLogin) as Switch
|
||||
val mHTTPSwitch = findViewById(R.id.withHttpLogin) as Switch
|
||||
val mLoginLayout = findViewById(R.id.loginLayout) as TextInputLayout
|
||||
val mHTTPLoginLayout = findViewById(R.id.httpLoginInput) as TextInputLayout
|
||||
val mPasswordLayout = findViewById(R.id.passwordLayout) as TextInputLayout
|
||||
val mHTTPPasswordLayout = findViewById(R.id.httpPasswordInput) as TextInputLayout
|
||||
val mEmailSignInButton = findViewById(R.id.email_sign_in_button) as Button
|
||||
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)
|
||||
val selfHostedSwitch: Switch = findViewById(R.id.withSelfhostedCert)
|
||||
val warningTextview: TextView = findViewById(R.id.warningText)
|
||||
|
||||
mPasswordView!!.setOnEditorActionListener(TextView.OnEditorActionListener { _, id, _ ->
|
||||
selfHostedSwitch.setOnCheckedChangeListener {_, b ->
|
||||
isWithSelfSignedCert = !isWithSelfSignedCert
|
||||
val visi: Int = if (b) View.VISIBLE else View.GONE
|
||||
|
||||
warningTextview.visibility = visi
|
||||
}
|
||||
|
||||
mPasswordView.setOnEditorActionListener(TextView.OnEditorActionListener { _, id, _ ->
|
||||
if (id == R.id.login || id == EditorInfo.IME_NULL) {
|
||||
attemptLogin()
|
||||
return@OnEditorActionListener true
|
||||
@ -92,26 +123,16 @@ class LoginActivity : AppCompatActivity() {
|
||||
|
||||
mSwitch.setOnCheckedChangeListener { _, b ->
|
||||
isWithLogin = !isWithLogin
|
||||
val visi: Int
|
||||
if (b) {
|
||||
visi = View.VISIBLE
|
||||
val visi: Int = if (b) View.VISIBLE else View.GONE
|
||||
|
||||
} else {
|
||||
visi = View.GONE
|
||||
}
|
||||
mLoginLayout.visibility = visi
|
||||
mPasswordLayout.visibility = visi
|
||||
}
|
||||
|
||||
mHTTPSwitch.setOnCheckedChangeListener { _, b ->
|
||||
isWithHTTPLogin = !isWithHTTPLogin
|
||||
val visi: Int
|
||||
if (b) {
|
||||
visi = View.VISIBLE
|
||||
val visi: Int = if (b) View.VISIBLE else View.GONE
|
||||
|
||||
} else {
|
||||
visi = View.GONE
|
||||
}
|
||||
mHTTPLoginLayout.visibility = visi
|
||||
mHTTPPasswordLayout.visibility = visi
|
||||
}
|
||||
@ -126,24 +147,24 @@ class LoginActivity : AppCompatActivity() {
|
||||
private fun attemptLogin() {
|
||||
|
||||
// Reset errors.
|
||||
mUrlView!!.error = null
|
||||
mLoginView!!.error = null
|
||||
mHTTPLoginView!!.error = null
|
||||
mPasswordView!!.error = null
|
||||
mHTTPPasswordView!!.error = null
|
||||
mUrlView.error = null
|
||||
mLoginView.error = null
|
||||
mHTTPLoginView.error = null
|
||||
mPasswordView.error = null
|
||||
mHTTPPasswordView.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 = mUrlView.text.toString()
|
||||
val login = mLoginView.text.toString()
|
||||
val httpLogin = mHTTPLoginView.text.toString()
|
||||
val password = mPasswordView.text.toString()
|
||||
val httpPassword = mHTTPPasswordView.text.toString()
|
||||
|
||||
var cancel = false
|
||||
var focusView: View? = null
|
||||
|
||||
if (!isUrlValid(url)) {
|
||||
mUrlView!!.error = getString(R.string.login_url_problem)
|
||||
if (!url.isBaseUrlValid()) {
|
||||
mUrlView.error = getString(R.string.login_url_problem)
|
||||
focusView = mUrlView
|
||||
cancel = true
|
||||
inValidCount++
|
||||
@ -151,8 +172,10 @@ class LoginActivity : AppCompatActivity() {
|
||||
val alertDialog = AlertDialog.Builder(this).create()
|
||||
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.setButton(
|
||||
AlertDialog.BUTTON_NEUTRAL,
|
||||
"OK",
|
||||
{ dialog, _ -> dialog.dismiss() })
|
||||
alertDialog.show()
|
||||
inValidCount = 0
|
||||
}
|
||||
@ -160,59 +183,65 @@ class LoginActivity : AppCompatActivity() {
|
||||
|
||||
if (isWithLogin || isWithHTTPLogin) {
|
||||
if (TextUtils.isEmpty(password)) {
|
||||
mPasswordView!!.error = getString(R.string.error_invalid_password)
|
||||
mPasswordView.error = getString(R.string.error_invalid_password)
|
||||
focusView = mPasswordView
|
||||
cancel = true
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(login)) {
|
||||
mLoginView!!.error = getString(R.string.error_field_required)
|
||||
mLoginView.error = getString(R.string.error_field_required)
|
||||
focusView = mLoginView
|
||||
cancel = true
|
||||
}
|
||||
}
|
||||
|
||||
if (cancel) {
|
||||
focusView!!.requestFocus()
|
||||
focusView?.requestFocus()
|
||||
} 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@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)
|
||||
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)
|
||||
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())
|
||||
mFirebaseAnalytics.logEvent(FirebaseAnalytics.Event.LOGIN, Bundle())
|
||||
goToMain()
|
||||
} else {
|
||||
preferenceError()
|
||||
preferenceError(Exception("No response body..."))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
|
||||
preferenceError()
|
||||
preferenceError(t)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -224,26 +253,34 @@ class LoginActivity : AppCompatActivity() {
|
||||
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
|
||||
}
|
||||
})
|
||||
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
|
||||
}
|
||||
})
|
||||
|
||||
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
|
||||
}
|
||||
})
|
||||
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
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
val inflater = menuInflater
|
||||
inflater.inflate(R.menu.login_menu, menu)
|
||||
menuInflater.inflate(R.menu.login_menu, menu)
|
||||
menu.findItem(R.id.loging_debug).isChecked = logErrors
|
||||
return true
|
||||
}
|
||||
|
||||
@ -251,10 +288,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.loging_debug -> {
|
||||
val newState = !item.isChecked
|
||||
item.isChecked = newState
|
||||
logErrors = newState
|
||||
editor.putBoolean("loging_debug", newState)
|
||||
editor.apply()
|
||||
return true
|
||||
}
|
||||
else -> return super.onOptionsItemSelected(item)
|
||||
|
@ -6,6 +6,7 @@ import android.preference.PreferenceManager
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
|
||||
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
@ -1,20 +1,118 @@
|
||||
package apps.amine.bou.readerforselfoss
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Drawable
|
||||
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)
|
||||
.apply(RequestOptions.fitCenterTransform()
|
||||
.placeholder(placeholder))
|
||||
.into(imageView)
|
||||
}
|
||||
|
||||
override fun cancel(imageView: 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)
|
||||
.addFlavor(getString(R.string.teal_orange_theme), R.style.NoBarTealOrange)
|
||||
.addFlavor(getString(R.string.teal_orange_dark_theme), R.style.NoBarTealOrangeDark)
|
||||
.addFlavor(getString(R.string.cyan_pink_theme), R.style.NoBarCyanPink)
|
||||
.addFlavor(getString(R.string.cyan_pink_dark_theme), R.style.NoBarCyanPinkDark)
|
||||
.addFlavor(getString(R.string.grey_orange_theme), R.style.NoBarGreyOrange)
|
||||
.addFlavor(getString(R.string.grey_orange_dark_theme), R.style.NoBarGreyOrangeDark)
|
||||
.addFlavor(getString(R.string.blue_amber_theme), R.style.NoBarBlueAmber)
|
||||
.addFlavor(getString(R.string.blue_amber_dark_theme), R.style.NoBarBlueAmberDark)
|
||||
.addFlavor(getString(R.string.indigo_pink_theme), R.style.NoBarIndigoPink)
|
||||
.addFlavor(getString(R.string.indigo_pink_dark_theme), R.style.NoBarIndigoPinkDark)
|
||||
.addFlavor(getString(R.string.red_teal_theme), R.style.NoBarRedTeal)
|
||||
.addFlavor(getString(R.string.red_teal_dark_theme), R.style.NoBarRedTealDark)
|
||||
.setSharedPreferences(PreferenceManager.getDefaultSharedPreferences(this))
|
||||
.initialize()
|
||||
}
|
||||
|
||||
private fun tryToHandleBug() {
|
||||
val oldHandler = Thread.getDefaultUncaughtExceptionHandler()
|
||||
|
||||
Thread.setDefaultUncaughtExceptionHandler { thread, e ->
|
||||
if (e is java.lang.NoClassDefFoundError && e.stackTrace.asList().any { it.toString().contains("android.view.ViewDebug") })
|
||||
Unit
|
||||
else
|
||||
oldHandler.uncaughtException(thread, e)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,18 +1,21 @@
|
||||
package apps.amine.bou.readerforselfoss
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageButton
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
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.openItemUrl
|
||||
import apps.amine.bou.readerforselfoss.utils.shareLink
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import com.ftinc.scoop.Scoop
|
||||
import org.sufficientlysecure.htmltextview.HtmlHttpImageGetter
|
||||
import org.sufficientlysecure.htmltextview.HtmlTextView
|
||||
import retrofit2.Call
|
||||
@ -22,60 +25,85 @@ import xyz.klinker.android.drag_dismiss.activity.DragDismissActivity
|
||||
|
||||
|
||||
class ReaderActivity : DragDismissActivity() {
|
||||
private var mCustomTabActivityHelper: CustomTabActivityHelper? = null
|
||||
private lateinit var mCustomTabActivityHelper: CustomTabActivityHelper
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
mCustomTabActivityHelper!!.bindCustomTabsService(this)
|
||||
mCustomTabActivityHelper.bindCustomTabsService(this)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
mCustomTabActivityHelper!!.unbindCustomTabsService(this)
|
||||
mCustomTabActivityHelper.unbindCustomTabsService(this)
|
||||
}
|
||||
|
||||
override fun onCreateContent(inflater: LayoutInflater, parent: ViewGroup, savedInstanceState: Bundle?): View {
|
||||
Scoop.getInstance().apply(this)
|
||||
val v = inflater.inflate(R.layout.activity_reader, parent, false)
|
||||
showProgressBar()
|
||||
|
||||
val image = v.findViewById(R.id.imageView) as ImageView
|
||||
val source = v.findViewById(R.id.source) as TextView
|
||||
val title = v.findViewById(R.id.title) as TextView
|
||||
val content = v.findViewById(R.id.content) as HtmlTextView
|
||||
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 parser = MercuryApi(BuildConfig.MERCURY_KEY)
|
||||
val browserBtn: ImageButton = v.findViewById(R.id.browserBtn)
|
||||
val shareBtn: ImageButton = v.findViewById(R.id.shareBtn)
|
||||
|
||||
val customTabsIntent = buildCustomTabsIntent(this@ReaderActivity)
|
||||
|
||||
val customTabsIntent = this@ReaderActivity.buildCustomTabsIntent()
|
||||
mCustomTabActivityHelper = CustomTabActivityHelper()
|
||||
mCustomTabActivityHelper!!.bindCustomTabsService(this)
|
||||
mCustomTabActivityHelper.bindCustomTabsService(this)
|
||||
|
||||
|
||||
parser.parseUrl(url).enqueue(object : Callback<ParsedContent> {
|
||||
override fun onResponse(call: Call<ParsedContent>, response: Response<ParsedContent>) {
|
||||
if (response.body() != null) {
|
||||
if (response.body() != null && response.body()!!.content != null && response.body()!!.content.isNotEmpty()) {
|
||||
source.text = response.body()!!.domain
|
||||
title.text = response.body()!!.title
|
||||
if (response.body()!!.content != null && !response.body()!!.content.isEmpty())
|
||||
content.setHtml(response.body()!!.content, HtmlHttpImageGetter(content, null, true))
|
||||
if (response.body()!!.content != null && !response.body()!!.content.isEmpty()) {
|
||||
try {
|
||||
content.setHtml(response.body()!!.content, HtmlHttpImageGetter(content, null, true))
|
||||
} catch (e: IndexOutOfBoundsException) {
|
||||
openInBrowserAfterFailing()
|
||||
}
|
||||
}
|
||||
if (response.body()!!.lead_image_url != null && !response.body()!!.lead_image_url.isEmpty())
|
||||
Glide.with(applicationContext).load(response.body()!!.lead_image_url).asBitmap().fitCenter().into(image)
|
||||
Glide
|
||||
.with(baseContext)
|
||||
.asBitmap()
|
||||
.load(response.body()!!.lead_image_url)
|
||||
.apply(RequestOptions.fitCenterTransform())
|
||||
.into(image)
|
||||
|
||||
shareBtn.setOnClickListener {
|
||||
this@ReaderActivity.shareLink(response.body()!!.url)
|
||||
}
|
||||
|
||||
browserBtn.setOnClickListener {
|
||||
this@ReaderActivity.openItemUrl(
|
||||
response.body()!!.url,
|
||||
customTabsIntent,
|
||||
false,
|
||||
false,
|
||||
this@ReaderActivity)
|
||||
}
|
||||
|
||||
hideProgressBar()
|
||||
} else {
|
||||
errorAfterMercuryCall()
|
||||
}
|
||||
} else openInBrowserAfterFailing()
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<ParsedContent>, t: Throwable) {
|
||||
errorAfterMercuryCall()
|
||||
}
|
||||
override fun onFailure(call: Call<ParsedContent>, t: Throwable) = openInBrowserAfterFailing()
|
||||
|
||||
private fun errorAfterMercuryCall() {
|
||||
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)
|
||||
}
|
||||
private fun openInBrowserAfterFailing() {
|
||||
this@ReaderActivity.openItemUrl(
|
||||
url,
|
||||
customTabsIntent,
|
||||
true,
|
||||
false,
|
||||
this@ReaderActivity
|
||||
)
|
||||
finish()
|
||||
}
|
||||
})
|
||||
|
@ -2,32 +2,45 @@ 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 apps.amine.bou.readerforselfoss.adapters.SourcesListAdapter
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.Sources
|
||||
|
||||
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
|
||||
|
||||
|
||||
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 onResume() {
|
||||
super.onResume()
|
||||
val mFab = findViewById(R.id.fab) as FloatingActionButton
|
||||
val mRecyclerView = findViewById(R.id.activity_sources) as RecyclerView
|
||||
val mFab: FloatingActionButton = findViewById(R.id.fab)
|
||||
val mRecyclerView: RecyclerView = findViewById(R.id.activity_sources)
|
||||
val mLayoutManager = LinearLayoutManager(this)
|
||||
val api = SelfossApi(this)
|
||||
|
||||
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)
|
||||
|
@ -1,18 +1,13 @@
|
||||
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.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
|
||||
@ -20,31 +15,40 @@ import android.widget.ImageView
|
||||
import android.widget.ImageView.ScaleType
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import apps.amine.bou.readerforselfoss.R
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.Item
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
|
||||
import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent
|
||||
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
|
||||
import apps.amine.bou.readerforselfoss.utils.openItemUrl
|
||||
|
||||
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.customtabs.CustomTabActivityHelper
|
||||
import apps.amine.bou.readerforselfoss.utils.glide.bitmapCenterCrop
|
||||
import apps.amine.bou.readerforselfoss.utils.glide.bitmapFitCenter
|
||||
import apps.amine.bou.readerforselfoss.utils.glide.circularBitmapDrawable
|
||||
import com.crashlytics.android.Crashlytics
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
class ItemCardAdapter(private val app: Activity, private val items: ArrayList<Item>, private val api: SelfossApi,
|
||||
private val helper: CustomTabActivityHelper, private val internalBrowser: Boolean,
|
||||
private val articleViewer: Boolean, private val fullHeightCards: Boolean) : RecyclerView.Adapter<ItemCardAdapter.ViewHolder>() {
|
||||
private val c: Context = app.applicationContext
|
||||
class ItemCardAdapter(private val app: Activity,
|
||||
private val items: ArrayList<Item>,
|
||||
private val api: SelfossApi,
|
||||
private val helper: CustomTabActivityHelper,
|
||||
private val internalBrowser: Boolean,
|
||||
private val articleViewer: Boolean,
|
||||
private val fullHeightCards: Boolean,
|
||||
private val appColors: AppColors,
|
||||
val debugReadingItems: Boolean,
|
||||
val userIdentifier: String) : RecyclerView.Adapter<ItemCardAdapter.ViewHolder>() {
|
||||
private val c: Context = app.baseContext
|
||||
private val generator: ColorGenerator = ColorGenerator.MATERIAL
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
@ -56,60 +60,37 @@ class ItemCardAdapter(private val app: Activity, private val items: ArrayList<It
|
||||
val itm = items[position]
|
||||
|
||||
|
||||
holder.saveBtn!!.isLiked = itm.starred
|
||||
holder.title!!.text = Html.fromHtml(itm.title)
|
||||
holder.saveBtn.isLiked = itm.starred
|
||||
holder.title.text = Html.fromHtml(itm.title)
|
||||
|
||||
var sourceAndDate = itm.sourcetitle
|
||||
val d: Long
|
||||
try {
|
||||
d = SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(itm.datetime).time
|
||||
sourceAndDate += " " + DateUtils.getRelativeTimeSpanString(
|
||||
d,
|
||||
Date().time,
|
||||
DateUtils.MINUTE_IN_MILLIS,
|
||||
DateUtils.FORMAT_ABBREV_RELATIVE
|
||||
)
|
||||
} catch (e: ParseException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
holder.sourceTitleAndDate!!.text = sourceAndDate
|
||||
holder.sourceTitleAndDate.text = itm.sourceAndDateText()
|
||||
|
||||
if (itm.getThumbnail(c).isEmpty()) {
|
||||
Glide.clear(holder.itemImage)
|
||||
holder.itemImage!!.setImageDrawable(null)
|
||||
Glide.with(c).clear(holder.itemImage)
|
||||
holder.itemImage.setImageDrawable(null)
|
||||
} else {
|
||||
if (fullHeightCards) {
|
||||
Glide.with(c).load(itm.getThumbnail(c)).asBitmap().fitCenter().into(holder.itemImage)
|
||||
c.bitmapFitCenter(itm.getThumbnail(c), holder.itemImage)
|
||||
} else {
|
||||
Glide.with(c).load(itm.getThumbnail(c)).asBitmap().centerCrop().into(holder.itemImage)
|
||||
c.bitmapCenterCrop(itm.getThumbnail(c), holder.itemImage)
|
||||
}
|
||||
}
|
||||
|
||||
val fHolder = holder
|
||||
if (itm.getIcon(c).isEmpty()) {
|
||||
val color = generator.getColor(itm.sourcetitle)
|
||||
val textDrawable = StringBuilder()
|
||||
for (s in itm.sourcetitle.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) {
|
||||
textDrawable.append(s[0])
|
||||
}
|
||||
|
||||
val builder = TextDrawable.builder().round()
|
||||
|
||||
val drawable = builder.build(textDrawable.toString(), color)
|
||||
holder.sourceImage!!.setImageDrawable(drawable)
|
||||
val drawable =
|
||||
TextDrawable
|
||||
.builder()
|
||||
.round()
|
||||
.build(itm.sourcetitle.toTextDrawableString(), color)
|
||||
holder.sourceImage.setImageDrawable(drawable)
|
||||
} else {
|
||||
|
||||
Glide.with(c).load(itm.getIcon(c)).asBitmap().centerCrop().into(object : BitmapImageViewTarget(holder.sourceImage) {
|
||||
override fun setResource(resource: Bitmap) {
|
||||
val circularBitmapDrawable = RoundedBitmapDrawableFactory.create(c.resources, resource)
|
||||
circularBitmapDrawable.isCircular = true
|
||||
fHolder.sourceImage!!.setImageDrawable(circularBitmapDrawable)
|
||||
}
|
||||
})
|
||||
c.circularBitmapDrawable(itm.getIcon(c), holder.sourceImage)
|
||||
}
|
||||
|
||||
holder.saveBtn!!.isLiked = itm.starred
|
||||
holder.saveBtn.isLiked = itm.starred
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
@ -135,7 +116,7 @@ class ItemCardAdapter(private val app: Activity, private val items: ArrayList<It
|
||||
}
|
||||
|
||||
val view = s.view
|
||||
val tv = view.findViewById(android.support.design.R.id.snackbar_text) as TextView
|
||||
val tv: TextView = view.findViewById(android.support.design.R.id.snackbar_text)
|
||||
tv.setTextColor(Color.WHITE)
|
||||
s.show()
|
||||
}
|
||||
@ -149,11 +130,31 @@ class ItemCardAdapter(private val app: Activity, private val items: ArrayList<It
|
||||
|
||||
api.markItem(i.id).enqueue(object : Callback<SuccessResponse> {
|
||||
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {
|
||||
if (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)
|
||||
@ -163,41 +164,42 @@ class ItemCardAdapter(private val app: Activity, private val items: ArrayList<It
|
||||
}
|
||||
|
||||
inner class ViewHolder(val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) {
|
||||
var saveBtn: LikeButton? = null
|
||||
var browserBtn: ImageButton? = null
|
||||
var shareBtn: ImageButton? = null
|
||||
var itemImage: ImageView? = null
|
||||
var sourceImage: ImageView? = null
|
||||
var title: TextView? = null
|
||||
var sourceTitleAndDate: TextView? = null
|
||||
lateinit var saveBtn: LikeButton
|
||||
lateinit var browserBtn: ImageButton
|
||||
lateinit var shareBtn: ImageButton
|
||||
lateinit var itemImage: ImageView
|
||||
lateinit var sourceImage: ImageView
|
||||
lateinit var title: TextView
|
||||
lateinit var sourceTitleAndDate: TextView
|
||||
|
||||
init {
|
||||
(mView.findViewById<CardView>(R.id.card)).setCardBackgroundColor(appColors.cardBackground)
|
||||
handleClickListeners()
|
||||
handleCustomTabActions()
|
||||
}
|
||||
|
||||
private fun handleClickListeners() {
|
||||
sourceImage = mView.findViewById(R.id.sourceImage) as ImageView
|
||||
itemImage = mView.findViewById(R.id.itemImage) as ImageView
|
||||
title = mView.findViewById(R.id.title) as TextView
|
||||
sourceTitleAndDate = mView.findViewById(R.id.sourceTitleAndDate) as TextView
|
||||
saveBtn = mView.findViewById(R.id.favButton) as LikeButton
|
||||
shareBtn = mView.findViewById(R.id.shareBtn) as ImageButton
|
||||
browserBtn = mView.findViewById(R.id.browserBtn) as ImageButton
|
||||
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
|
||||
itemImage.maxHeight = c.resources.getDimension(R.dimen.card_image_max_height).toInt()
|
||||
itemImage.scaleType = ScaleType.CENTER_CROP
|
||||
}
|
||||
|
||||
saveBtn!!.setOnLikeListener(object : OnLikeListener {
|
||||
saveBtn.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
|
||||
saveBtn.isLiked = false
|
||||
Toast.makeText(c, R.string.cant_mark_favortie, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
})
|
||||
@ -209,43 +211,32 @@ class ItemCardAdapter(private val app: Activity, private val items: ArrayList<It
|
||||
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {}
|
||||
|
||||
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
|
||||
saveBtn!!.isLiked = true
|
||||
saveBtn.isLiked = true
|
||||
Toast.makeText(c, R.string.cant_unmark_favortie, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
shareBtn!!.setOnClickListener {
|
||||
val i = items[adapterPosition]
|
||||
val sendIntent = Intent()
|
||||
sendIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
sendIntent.action = Intent.ACTION_SEND
|
||||
sendIntent.putExtra(Intent.EXTRA_TEXT, i.getLinkDecoded())
|
||||
sendIntent.type = "text/plain"
|
||||
c.startActivity(Intent.createChooser(sendIntent, c.getString(R.string.share)).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
|
||||
shareBtn.setOnClickListener {
|
||||
c.shareLink(items[adapterPosition].getLinkDecoded())
|
||||
}
|
||||
|
||||
browserBtn!!.setOnClickListener {
|
||||
val i = items[adapterPosition]
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
intent.data = Uri.parse(i.getLinkDecoded())
|
||||
c.startActivity(intent)
|
||||
browserBtn.setOnClickListener {
|
||||
c.openInBrowser(items[adapterPosition])
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCustomTabActions() {
|
||||
val customTabsIntent = buildCustomTabsIntent(c)
|
||||
val customTabsIntent = c.buildCustomTabsIntent()
|
||||
helper.bindCustomTabsService(app)
|
||||
|
||||
mView.setOnClickListener {
|
||||
openItemUrl(items[adapterPosition],
|
||||
customTabsIntent,
|
||||
internalBrowser,
|
||||
articleViewer,
|
||||
app,
|
||||
c)
|
||||
c.openItemUrl(items[adapterPosition].getLinkDecoded(),
|
||||
customTabsIntent,
|
||||
internalBrowser,
|
||||
articleViewer,
|
||||
app)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,47 +3,49 @@ 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 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.openItemUrl
|
||||
|
||||
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.utils.*
|
||||
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 com.crashlytics.android.Crashlytics
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
class ItemListAdapter(private val app: Activity, private val items: ArrayList<Item>, private val api: SelfossApi,
|
||||
private val helper: CustomTabActivityHelper, private val clickBehavior: Boolean,
|
||||
private val internalBrowser: Boolean, private val articleViewer: Boolean) : RecyclerView.Adapter<ItemListAdapter.ViewHolder>() {
|
||||
|
||||
class ItemListAdapter(private val app: Activity,
|
||||
private val items: ArrayList<Item>,
|
||||
private val api: SelfossApi,
|
||||
private val helper: CustomTabActivityHelper,
|
||||
private val clickBehavior: Boolean,
|
||||
private val internalBrowser: Boolean,
|
||||
private val articleViewer: Boolean,
|
||||
val debugReadingItems: Boolean,
|
||||
val userIdentifier: String) : RecyclerView.Adapter<ItemListAdapter.ViewHolder>() {
|
||||
private val generator: ColorGenerator = ColorGenerator.MATERIAL
|
||||
private val c: Context = app.applicationContext
|
||||
private val c: Context = app.baseContext
|
||||
private val bars: ArrayList<Boolean> = ArrayList(Collections.nCopies(items.size + 1, false))
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
@ -55,41 +57,27 @@ class ItemListAdapter(private val app: Activity, private val items: ArrayList<It
|
||||
val itm = items[position]
|
||||
|
||||
|
||||
holder.saveBtn!!.isLiked = itm.starred
|
||||
holder.title!!.text = Html.fromHtml(itm.title)
|
||||
holder.saveBtn.isLiked = itm.starred
|
||||
holder.title.text = Html.fromHtml(itm.title)
|
||||
|
||||
var sourceAndDate = itm.sourcetitle
|
||||
val d: Long
|
||||
try {
|
||||
d = SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(itm.datetime).time
|
||||
sourceAndDate += " " + DateUtils.getRelativeTimeSpanString(
|
||||
d,
|
||||
Date().time,
|
||||
DateUtils.MINUTE_IN_MILLIS,
|
||||
DateUtils.FORMAT_ABBREV_RELATIVE
|
||||
)
|
||||
} catch (e: ParseException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
holder.sourceTitleAndDate!!.text = sourceAndDate
|
||||
holder.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.sourceImage.layoutParams as ViewGroup.MarginLayoutParams
|
||||
params.height = sizeInDp
|
||||
params.width = sizeInDp
|
||||
params.setMargins(marginInDp, 0, 0, 0)
|
||||
holder.sourceImage!!.layoutParams = params
|
||||
holder.sourceImage.layoutParams = params
|
||||
|
||||
if (itm.getIcon(c).isEmpty()) {
|
||||
val color = generator.getColor(itm.sourcetitle)
|
||||
@ -101,34 +89,20 @@ class ItemListAdapter(private val app: Activity, private val items: ArrayList<It
|
||||
val builder = TextDrawable.builder().round()
|
||||
|
||||
val drawable = builder.build(textDrawable.toString(), color)
|
||||
holder.sourceImage!!.setImageDrawable(drawable)
|
||||
holder.sourceImage.setImageDrawable(drawable)
|
||||
} else {
|
||||
|
||||
val fHolder = holder
|
||||
Glide.with(c).load(itm.getIcon(c)).asBitmap().centerCrop().into(object : BitmapImageViewTarget(holder.sourceImage) {
|
||||
override fun setResource(resource: Bitmap) {
|
||||
val circularBitmapDrawable = RoundedBitmapDrawableFactory.create(c.resources, resource)
|
||||
circularBitmapDrawable.isCircular = true
|
||||
fHolder.sourceImage!!.setImageDrawable(circularBitmapDrawable)
|
||||
}
|
||||
})
|
||||
c.circularBitmapDrawable(itm.getIcon(c), holder.sourceImage)
|
||||
}
|
||||
} else {
|
||||
Glide.with(c).load(itm.getThumbnail(c)).asBitmap().centerCrop().into(holder.sourceImage)
|
||||
c.bitmapCenterCrop(itm.getThumbnail(c), holder.sourceImage)
|
||||
}
|
||||
|
||||
if (bars[position]) {
|
||||
holder.actionBar!!.visibility = View.VISIBLE
|
||||
} else {
|
||||
holder.actionBar!!.visibility = View.GONE
|
||||
}
|
||||
if (bars[position]) holder.actionBar.visibility = View.VISIBLE else holder.actionBar.visibility = View.GONE
|
||||
|
||||
holder.saveBtn!!.isLiked = itm.starred
|
||||
holder.saveBtn.isLiked = itm.starred
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return items.size
|
||||
}
|
||||
override fun getItemCount(): Int = items.size
|
||||
|
||||
|
||||
private fun doUnmark(i: Item, position: Int) {
|
||||
@ -150,7 +124,7 @@ class ItemListAdapter(private val app: Activity, private val items: ArrayList<It
|
||||
}
|
||||
|
||||
val view = s.view
|
||||
val tv = view.findViewById(android.support.design.R.id.snackbar_text) as TextView
|
||||
val tv: TextView = view.findViewById(android.support.design.R.id.snackbar_text)
|
||||
tv.setTextColor(Color.WHITE)
|
||||
s.show()
|
||||
}
|
||||
@ -164,11 +138,31 @@ class ItemListAdapter(private val app: Activity, private val items: ArrayList<It
|
||||
|
||||
api.markItem(i.id).enqueue(object : Callback<SuccessResponse> {
|
||||
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {
|
||||
if (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)
|
||||
@ -178,13 +172,13 @@ class ItemListAdapter(private val app: Activity, private val items: ArrayList<It
|
||||
}
|
||||
|
||||
inner class ViewHolder(val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) {
|
||||
var saveBtn: LikeButton? = null
|
||||
var browserBtn: ImageButton? = null
|
||||
var shareBtn: ImageButton? = null
|
||||
var actionBar: RelativeLayout? = null
|
||||
var sourceImage: ImageView? = null
|
||||
var title: TextView? = null
|
||||
var sourceTitleAndDate: TextView? = null
|
||||
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()
|
||||
@ -192,23 +186,23 @@ class ItemListAdapter(private val app: Activity, private val items: ArrayList<It
|
||||
}
|
||||
|
||||
private fun handleClickListeners() {
|
||||
actionBar = mView.findViewById(R.id.actionBar) as RelativeLayout
|
||||
sourceImage = mView.findViewById(R.id.itemImage) as ImageView
|
||||
title = mView.findViewById(R.id.title) as TextView
|
||||
sourceTitleAndDate = mView.findViewById(R.id.sourceTitleAndDate) as TextView
|
||||
saveBtn = mView.findViewById(R.id.favButton) as LikeButton
|
||||
shareBtn = mView.findViewById(R.id.shareBtn) as ImageButton
|
||||
browserBtn = mView.findViewById(R.id.browserBtn) as ImageButton
|
||||
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 {
|
||||
saveBtn.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
|
||||
saveBtn.isLiked = false
|
||||
Toast.makeText(c, R.string.cant_mark_favortie, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
})
|
||||
@ -220,46 +214,36 @@ class ItemListAdapter(private val app: Activity, private val items: ArrayList<It
|
||||
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {}
|
||||
|
||||
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
|
||||
saveBtn!!.isLiked = true
|
||||
saveBtn.isLiked = true
|
||||
Toast.makeText(c, R.string.cant_unmark_favortie, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
shareBtn!!.setOnClickListener {
|
||||
val i = items[adapterPosition]
|
||||
val sendIntent = Intent()
|
||||
sendIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
sendIntent.action = Intent.ACTION_SEND
|
||||
sendIntent.putExtra(Intent.EXTRA_TEXT, i.getLinkDecoded())
|
||||
sendIntent.type = "text/plain"
|
||||
c.startActivity(Intent.createChooser(sendIntent, c.getString(R.string.share)).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
|
||||
shareBtn.setOnClickListener {
|
||||
c.shareLink(items[adapterPosition].getLinkDecoded())
|
||||
}
|
||||
|
||||
browserBtn!!.setOnClickListener {
|
||||
val i = items[adapterPosition]
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
intent.data = Uri.parse(i.getLinkDecoded())
|
||||
c.startActivity(intent)
|
||||
browserBtn.setOnClickListener {
|
||||
c.openInBrowser(items[adapterPosition])
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun handleCustomTabActions() {
|
||||
val customTabsIntent = buildCustomTabsIntent(c)
|
||||
val customTabsIntent = c.buildCustomTabsIntent()
|
||||
helper.bindCustomTabsService(app)
|
||||
|
||||
|
||||
if (!clickBehavior) {
|
||||
mView.setOnClickListener {
|
||||
openItemUrl(items[adapterPosition],
|
||||
customTabsIntent,
|
||||
internalBrowser,
|
||||
articleViewer,
|
||||
app,
|
||||
c)
|
||||
c.openItemUrl(items[adapterPosition].getLinkDecoded(),
|
||||
customTabsIntent,
|
||||
internalBrowser,
|
||||
articleViewer,
|
||||
app)
|
||||
}
|
||||
mView.setOnLongClickListener {
|
||||
actionBarShowHide()
|
||||
@ -268,12 +252,11 @@ class ItemListAdapter(private val app: Activity, private val items: ArrayList<It
|
||||
} else {
|
||||
mView.setOnClickListener { actionBarShowHide() }
|
||||
mView.setOnLongClickListener {
|
||||
openItemUrl(items[adapterPosition],
|
||||
customTabsIntent,
|
||||
internalBrowser,
|
||||
articleViewer,
|
||||
app,
|
||||
c)
|
||||
c.openItemUrl(items[adapterPosition].getLinkDecoded(),
|
||||
customTabsIntent,
|
||||
internalBrowser,
|
||||
articleViewer,
|
||||
app)
|
||||
true
|
||||
}
|
||||
}
|
||||
@ -281,10 +264,7 @@ class ItemListAdapter(private val app: Activity, private val items: ArrayList<It
|
||||
|
||||
private fun actionBarShowHide() {
|
||||
bars[adapterPosition] = true
|
||||
if (actionBar!!.visibility == View.GONE)
|
||||
actionBar!!.visibility = View.VISIBLE
|
||||
else
|
||||
actionBar!!.visibility = View.GONE
|
||||
if (actionBar.visibility == View.GONE) actionBar.visibility = View.VISIBLE else actionBar.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,9 +2,7 @@ package apps.amine.bou.readerforselfoss.adapters
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.support.constraint.ConstraintLayout
|
||||
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
@ -12,19 +10,24 @@ 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 com.amulyakhare.textdrawable.TextDrawable
|
||||
import com.amulyakhare.textdrawable.util.ColorGenerator
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.target.BitmapImageViewTarget
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
|
||||
class SourcesListAdapter(private val app: Activity, private val items: ArrayList<Sources>, private val api: SelfossApi) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>() {
|
||||
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
|
||||
|
||||
|
||||
class SourcesListAdapter(private val app: Activity,
|
||||
private val items: ArrayList<Sources>,
|
||||
private val api: SelfossApi) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>() {
|
||||
private val c: Context = app.baseContext
|
||||
private val generator: ColorGenerator = ColorGenerator.MATERIAL
|
||||
|
||||
@ -36,29 +39,20 @@ class SourcesListAdapter(private val app: Activity, private val items: ArrayList
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val itm = items[position]
|
||||
|
||||
val fHolder = holder
|
||||
if (itm.getIcon(c).isEmpty()) {
|
||||
val color = generator.getColor(itm.title)
|
||||
val textDrawable = StringBuilder()
|
||||
for (s in itm.title.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) {
|
||||
textDrawable.append(s[0])
|
||||
}
|
||||
|
||||
val builder = TextDrawable.builder().round()
|
||||
|
||||
val drawable = builder.build(textDrawable.toString(), color)
|
||||
holder.sourceImage!!.setImageDrawable(drawable)
|
||||
val drawable =
|
||||
TextDrawable
|
||||
.builder()
|
||||
.round()
|
||||
.build(itm.title.toTextDrawableString(), color)
|
||||
holder.sourceImage.setImageDrawable(drawable)
|
||||
} else {
|
||||
Glide.with(c).load(itm.getIcon(c)).asBitmap().centerCrop().into(object : BitmapImageViewTarget(holder.sourceImage) {
|
||||
override fun setResource(resource: Bitmap) {
|
||||
val circularBitmapDrawable = RoundedBitmapDrawableFactory.create(c.resources, resource)
|
||||
circularBitmapDrawable.isCircular = true
|
||||
fHolder.sourceImage!!.setImageDrawable(circularBitmapDrawable)
|
||||
}
|
||||
})
|
||||
c.circularBitmapDrawable(itm.getIcon(c), holder.sourceImage)
|
||||
}
|
||||
|
||||
holder.sourceTitle!!.text = itm.title
|
||||
holder.sourceTitle.text = itm.title
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
@ -66,8 +60,8 @@ class SourcesListAdapter(private val app: Activity, private val items: ArrayList
|
||||
}
|
||||
|
||||
inner class ViewHolder(internal val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) {
|
||||
var sourceImage: ImageView? = null
|
||||
var sourceTitle: TextView? = null
|
||||
lateinit var sourceImage: ImageView
|
||||
lateinit var sourceTitle: TextView
|
||||
|
||||
init {
|
||||
|
||||
@ -75,10 +69,10 @@ class SourcesListAdapter(private val app: Activity, private val items: ArrayList
|
||||
}
|
||||
|
||||
private fun handleClickListeners() {
|
||||
sourceImage = mView.findViewById(R.id.itemImage) as ImageView
|
||||
sourceTitle = mView.findViewById(R.id.sourceTitle) as TextView
|
||||
sourceImage = mView.findViewById(R.id.itemImage)
|
||||
sourceTitle = mView.findViewById(R.id.sourceTitle)
|
||||
|
||||
val deleteBtn = mView.findViewById(R.id.deleteBtn) as Button
|
||||
val deleteBtn: Button = mView.findViewById(R.id.deleteBtn)
|
||||
|
||||
deleteBtn.setOnClickListener {
|
||||
val (id) = items[adapterPosition]
|
||||
@ -89,12 +83,12 @@ class SourcesListAdapter(private val app: Activity, private val items: ArrayList
|
||||
notifyItemRemoved(adapterPosition)
|
||||
notifyItemRangeChanged(adapterPosition, itemCount)
|
||||
} else {
|
||||
Toast.makeText(app, "Petit soucis lors de la suppression de la source.", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(app, R.string.can_delete_source, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
|
||||
Toast.makeText(app, "Petit soucis lors de la suppression de la source.", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(app, R.string.can_delete_source, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package apps.amine.bou.readerforselfoss.api.mercury
|
||||
|
||||
|
||||
import com.google.gson.GsonBuilder
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
@ -9,6 +8,7 @@ import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
|
||||
|
||||
|
||||
class MercuryApi(private val key: String) {
|
||||
private val service: MercuryService
|
||||
|
||||
@ -21,8 +21,13 @@ class MercuryApi(private val key: String) {
|
||||
val gson = GsonBuilder()
|
||||
.setLenient()
|
||||
.create()
|
||||
val retrofit = Retrofit.Builder().baseUrl("https://mercury.postlight.com").client(client)
|
||||
.addConverterFactory(GsonConverterFactory.create(gson)).build()
|
||||
val retrofit =
|
||||
Retrofit
|
||||
.Builder()
|
||||
.baseUrl("https://mercury.postlight.com")
|
||||
.client(client)
|
||||
.addConverterFactory(GsonConverterFactory.create(gson))
|
||||
.build()
|
||||
service = retrofit.create(MercuryService::class.java)
|
||||
}
|
||||
|
||||
|
@ -2,19 +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> {
|
||||
@ -24,17 +25,17 @@ class ParsedContent(val title: String,
|
||||
}
|
||||
|
||||
constructor(source: Parcel) : this(
|
||||
title = source.readString(),
|
||||
content = source.readString(),
|
||||
date_published = source.readString(),
|
||||
lead_image_url = source.readString(),
|
||||
dek = source.readString(),
|
||||
url = source.readString(),
|
||||
domain = source.readString(),
|
||||
excerpt = source.readString(),
|
||||
total_pages = source.readInt(),
|
||||
rendered_pages = source.readInt(),
|
||||
next_page_url = source.readString()
|
||||
title = source.readString(),
|
||||
content = source.readString(),
|
||||
date_published = source.readString(),
|
||||
lead_image_url = source.readString(),
|
||||
dek = source.readString(),
|
||||
url = source.readString(),
|
||||
domain = source.readString(),
|
||||
excerpt = source.readString(),
|
||||
total_pages = source.readInt(),
|
||||
rendered_pages = source.readInt(),
|
||||
next_page_url = source.readString()
|
||||
)
|
||||
|
||||
override fun describeContents() = 0
|
||||
|
@ -7,6 +7,7 @@ import retrofit2.http.Header
|
||||
import retrofit2.http.Query
|
||||
|
||||
|
||||
|
||||
interface MercuryService {
|
||||
@GET("parser")
|
||||
fun parseUrl(@Query("url") url: String, @Header("x-api-key") key: String): Call<ParsedContent>
|
||||
|
@ -1,10 +1,12 @@
|
||||
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 java.lang.reflect.Type
|
||||
|
||||
|
||||
|
||||
internal class BooleanTypeAdapter : JsonDeserializer<Boolean> {
|
||||
|
@ -1,8 +1,9 @@
|
||||
package apps.amine.bou.readerforselfoss.api.selfoss
|
||||
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import apps.amine.bou.readerforselfoss.utils.Config
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
import com.burgstaller.okhttp.AuthenticationCacheInterceptor
|
||||
import com.burgstaller.okhttp.CachingAuthenticatorDecorator
|
||||
import com.burgstaller.okhttp.DispatchingAuthenticator
|
||||
@ -12,99 +13,118 @@ import com.burgstaller.okhttp.digest.Credentials
|
||||
import com.burgstaller.okhttp.digest.DigestAuthenticator
|
||||
import com.google.gson.GsonBuilder
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Call
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
import apps.amine.bou.readerforselfoss.utils.Config
|
||||
import apps.amine.bou.readerforselfoss.utils.getUnsafeHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
|
||||
|
||||
class SelfossApi(c: Context) {
|
||||
|
||||
private val service: SelfossService
|
||||
|
||||
// codebeat:disable[ARITY,TOO_MANY_FUNCTIONS]
|
||||
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
|
||||
|
||||
init {
|
||||
fun OkHttpClient.Builder.maybeWithSelfSigned(isWithSelfSignedCert: Boolean): OkHttpClient.Builder =
|
||||
if (isWithSelfSignedCert) {
|
||||
getUnsafeHttpClient()
|
||||
} else {
|
||||
this
|
||||
}
|
||||
|
||||
val interceptor = HttpLoggingInterceptor()
|
||||
interceptor.level = HttpLoggingInterceptor.Level.BODY
|
||||
fun Credentials.createAuthenticator(): DispatchingAuthenticator =
|
||||
DispatchingAuthenticator.Builder()
|
||||
.with("digest", DigestAuthenticator(this))
|
||||
.with("basic", BasicAuthenticator(this))
|
||||
.build()
|
||||
|
||||
val httpBuilder = OkHttpClient.Builder()
|
||||
fun DispatchingAuthenticator.getHttpClien(isWithSelfSignedCert: Boolean): OkHttpClient.Builder {
|
||||
val authCache = ConcurrentHashMap<String, CachingAuthenticator>()
|
||||
|
||||
val httpUserName = config.httpUserLogin
|
||||
val httpPassword = config.httpUserPassword
|
||||
|
||||
val credentials = Credentials(httpUserName, httpPassword)
|
||||
val basicAuthenticator = BasicAuthenticator(credentials)
|
||||
val digestAuthenticator = DigestAuthenticator(credentials)
|
||||
|
||||
// note that all auth schemes should be registered as lowercase!
|
||||
val authenticator = DispatchingAuthenticator.Builder()
|
||||
.with("digest", digestAuthenticator)
|
||||
.with("basic", basicAuthenticator)
|
||||
.build()
|
||||
|
||||
val client = httpBuilder
|
||||
.authenticator(CachingAuthenticatorDecorator(authenticator, authCache))
|
||||
.addInterceptor(AuthenticationCacheInterceptor(authCache))
|
||||
.addInterceptor(interceptor)
|
||||
.build()
|
||||
return OkHttpClient
|
||||
.Builder()
|
||||
.maybeWithSelfSigned(isWithSelfSignedCert)
|
||||
.authenticator(CachingAuthenticatorDecorator(this, authCache))
|
||||
.addInterceptor(AuthenticationCacheInterceptor(authCache))
|
||||
}
|
||||
|
||||
|
||||
val builder = GsonBuilder()
|
||||
builder.registerTypeAdapter(Boolean::class.javaPrimitiveType, BooleanTypeAdapter())
|
||||
init {
|
||||
userName = config.userLogin
|
||||
password = config.userPassword
|
||||
|
||||
val gson = builder
|
||||
val authenticator =
|
||||
Credentials(
|
||||
config.httpUserLogin,
|
||||
config.httpUserPassword
|
||||
).createAuthenticator()
|
||||
|
||||
val gson =
|
||||
GsonBuilder()
|
||||
.registerTypeAdapter(Boolean::class.javaPrimitiveType, BooleanTypeAdapter())
|
||||
.setLenient()
|
||||
.create()
|
||||
|
||||
userName = config.userLogin
|
||||
password = config.userPassword
|
||||
val retrofit = Retrofit.Builder().baseUrl(config.baseUrl).client(client)
|
||||
.addConverterFactory(GsonConverterFactory.create(gson)).build()
|
||||
service = retrofit.create(SelfossService::class.java)
|
||||
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(httpClient.build())
|
||||
.addConverterFactory(GsonConverterFactory.create(gson))
|
||||
.build()
|
||||
service = retrofit.create(SelfossService::class.java)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Config.logoutAndRedirect(c, callingActivity, config.settings.edit(), baseUrlFail = true)
|
||||
}
|
||||
}
|
||||
|
||||
fun login(): Call<SuccessResponse> {
|
||||
return service.loginToSelfoss(config.userLogin, config.userPassword)
|
||||
}
|
||||
fun login(): Call<SuccessResponse> =
|
||||
service.loginToSelfoss(config.userLogin, config.userPassword)
|
||||
|
||||
val readItems: Call<List<Item>>
|
||||
get() = getItems("read")
|
||||
fun readItems(tag: String?, sourceId: Long?, search: String?, itemsNumber: Int, offset: Int): Call<List<Item>> =
|
||||
getItems("read", tag, sourceId, search, itemsNumber, offset)
|
||||
|
||||
val unreadItems: Call<List<Item>>
|
||||
get() = getItems("unread")
|
||||
fun newItems(tag: String?, sourceId: Long?, search: String?, itemsNumber: Int, offset: Int): Call<List<Item>> =
|
||||
getItems("unread", tag, sourceId, search, itemsNumber, offset)
|
||||
|
||||
val starredItems: Call<List<Item>>
|
||||
get() = getItems("starred")
|
||||
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): Call<List<Item>> {
|
||||
return service.getItems(type, 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> {
|
||||
return service.markAsRead(itemId, userName, password)
|
||||
}
|
||||
fun markItem(itemId: String): Call<SuccessResponse> =
|
||||
service.markAsRead(itemId, userName, password)
|
||||
|
||||
fun unmarkItem(itemId: String): Call<SuccessResponse> {
|
||||
return service.unmarkAsRead(itemId, userName, password)
|
||||
}
|
||||
fun unmarkItem(itemId: String): Call<SuccessResponse> =
|
||||
service.unmarkAsRead(itemId, userName, password)
|
||||
|
||||
fun readAll(ids: List<String>): Call<SuccessResponse> {
|
||||
return service.markAllAsRead(ids, userName, password)
|
||||
}
|
||||
fun readAll(ids: List<String>): Call<SuccessResponse> =
|
||||
service.markAllAsRead(ids, userName, password)
|
||||
|
||||
fun starrItem(itemId: String): Call<SuccessResponse> {
|
||||
return service.starr(itemId, userName, password)
|
||||
}
|
||||
fun starrItem(itemId: String): Call<SuccessResponse> =
|
||||
service.starr(itemId, userName, password)
|
||||
|
||||
|
||||
fun unstarrItem(itemId: String): Call<SuccessResponse> {
|
||||
return service.unstarr(itemId, userName, password)
|
||||
}
|
||||
fun unstarrItem(itemId: String): Call<SuccessResponse> =
|
||||
service.unstarr(itemId, userName, password)
|
||||
|
||||
val stats: Call<Stats>
|
||||
get() = service.stats(userName, password)
|
||||
@ -112,23 +132,21 @@ class SelfossApi(c: Context) {
|
||||
val tags: Call<List<Tag>>
|
||||
get() = service.tags(userName, password)
|
||||
|
||||
fun update(): Call<String> {
|
||||
return service.update(userName, password)
|
||||
}
|
||||
fun update(): Call<String> =
|
||||
service.update(userName, password)
|
||||
|
||||
val sources: Call<List<Sources>>
|
||||
get() = service.sources(userName, password)
|
||||
|
||||
fun deleteSource(id: String): Call<SuccessResponse> {
|
||||
return service.deleteSource(id, userName, password)
|
||||
}
|
||||
fun deleteSource(id: String): Call<SuccessResponse> =
|
||||
service.deleteSource(id, userName, password)
|
||||
|
||||
fun spouts(): Call<Map<String, Spout>> {
|
||||
return service.spouts(userName, password)
|
||||
}
|
||||
fun spouts(): Call<Map<String, Spout>> =
|
||||
service.spouts(userName, password)
|
||||
|
||||
fun createSource(title: String, url: String, spout: String, tags: String, filter: String): Call<SuccessResponse> {
|
||||
return service.createSource(title, url, spout, tags, filter, userName, password)
|
||||
}
|
||||
fun createSource(title: String, url: String, spout: String, tags: String, filter: String): Call<SuccessResponse> =
|
||||
service.createSource(title, url, spout, tags, filter, userName, password)
|
||||
|
||||
}
|
||||
|
||||
// codebeat:enable[ARITY,TOO_MANY_FUNCTIONS]
|
@ -4,36 +4,43 @@ import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
|
||||
import apps.amine.bou.readerforselfoss.utils.Config
|
||||
import apps.amine.bou.readerforselfoss.utils.isEmptyOrNullOrNullString
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
|
||||
private fun constructUrl(config: Config?, path: String, file: String): String {
|
||||
val baseUriBuilder = Uri.parse(config!!.baseUrl).buildUpon()
|
||||
baseUriBuilder.appendPath(path).appendPath(file)
|
||||
|
||||
return if (isEmptyOrNullOrNullString(file)) ""
|
||||
return if (file.isEmptyOrNullOrNullString()) ""
|
||||
else baseUriBuilder.toString()
|
||||
}
|
||||
|
||||
|
||||
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 {
|
||||
@ -45,15 +52,15 @@ 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("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
|
||||
|
||||
@ -65,15 +72,15 @@ 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(),
|
||||
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
|
||||
@ -107,14 +114,14 @@ 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
|
||||
|
@ -1,6 +1,5 @@
|
||||
package apps.amine.bou.readerforselfoss.api.selfoss
|
||||
|
||||
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.DELETE
|
||||
import retrofit2.http.Field
|
||||
@ -11,55 +10,88 @@ 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("username") username: String, @Query("password") password: String): Call<List<Item>>
|
||||
fun getItems(@Query("type") type: String,
|
||||
@Query("tag") tag: String?,
|
||||
@Query("source") source: Long?,
|
||||
@Query("search") search: String?,
|
||||
@Query("username") username: String,
|
||||
@Query("password") password: String,
|
||||
@Query("items") items: Int,
|
||||
@Query("offset") offset: Int): Call<List<Item>>
|
||||
|
||||
@POST("mark/{id}")
|
||||
fun markAsRead(@Path("id") id: String, @Query("username") username: String, @Query("password") password: String): Call<SuccessResponse>
|
||||
fun markAsRead(@Path("id") id: String,
|
||||
@Query("username") username: String,
|
||||
@Query("password") password: String): Call<SuccessResponse>
|
||||
|
||||
|
||||
@POST("unmark/{id}")
|
||||
fun unmarkAsRead(@Path("id") id: String, @Query("username") username: String, @Query("password") password: String): Call<SuccessResponse>
|
||||
fun unmarkAsRead(@Path("id") id: String,
|
||||
@Query("username") username: String,
|
||||
@Query("password") password: String): Call<SuccessResponse>
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST("mark")
|
||||
fun markAllAsRead(@Field("ids[]") ids: List<String>, @Query("username") username: String, @Query("password") password: String): Call<SuccessResponse>
|
||||
fun markAllAsRead(@Field("ids[]") ids: List<String>,
|
||||
@Query("username") username: String,
|
||||
@Query("password") password: String): Call<SuccessResponse>
|
||||
|
||||
|
||||
@POST("starr/{id}")
|
||||
fun starr(@Path("id") id: String, @Query("username") username: String, @Query("password") password: String): Call<SuccessResponse>
|
||||
fun starr(@Path("id") id: String,
|
||||
@Query("username") username: String,
|
||||
@Query("password") password: String): Call<SuccessResponse>
|
||||
|
||||
|
||||
@POST("unstarr/{id}")
|
||||
fun unstarr(@Path("id") id: String, @Query("username") username: String, @Query("password") password: String): Call<SuccessResponse>
|
||||
fun unstarr(@Path("id") id: String,
|
||||
@Query("username") username: String,
|
||||
@Query("password") password: String): Call<SuccessResponse>
|
||||
|
||||
|
||||
@GET("stats")
|
||||
fun stats(@Query("username") username: String, @Query("password") password: String): Call<Stats>
|
||||
fun stats(@Query("username") username: String,
|
||||
@Query("password") password: String): Call<Stats>
|
||||
|
||||
|
||||
@GET("tags")
|
||||
fun tags(@Query("username") username: String, @Query("password") password: String): Call<List<Tag>>
|
||||
fun tags(@Query("username") username: String,
|
||||
@Query("password") password: String): Call<List<Tag>>
|
||||
|
||||
|
||||
@GET("update")
|
||||
fun update(@Query("username") username: String, @Query("password") password: String): Call<String>
|
||||
fun update(@Query("username") username: String,
|
||||
@Query("password") password: String): Call<String>
|
||||
|
||||
@GET("sources/spouts")
|
||||
fun spouts(@Query("username") username: String, @Query("password") password: String): Call<Map<String, Spout>>
|
||||
fun spouts(@Query("username") username: String,
|
||||
@Query("password") password: String): Call<Map<String, Spout>>
|
||||
|
||||
@GET("sources/list")
|
||||
fun sources(@Query("username") username: String, @Query("password") password: String): Call<List<Sources>>
|
||||
fun sources(@Query("username") username: String,
|
||||
@Query("password") password: String): Call<List<Sources>>
|
||||
|
||||
|
||||
@DELETE("source/{id}")
|
||||
fun deleteSource(@Path("id") id: String, @Query("username") username: String, @Query("password") password: String): Call<SuccessResponse>
|
||||
fun deleteSource(@Path("id") id: String,
|
||||
@Query("username") username: String,
|
||||
@Query("password") password: String): Call<SuccessResponse>
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST("source")
|
||||
fun createSource(@Field("title") title: String, @Field("url") url: String, @Field("spout") spout: String, @Field("tags") tags: String, @Field("filter") filter: String, @Query("username") username: String, @Query("password") password: String): Call<SuccessResponse>
|
||||
fun createSource(@Field("title") title: String,
|
||||
@Field("url") url: String,
|
||||
@Field("spout") spout: String,
|
||||
@Field("tags") tags: String,
|
||||
@Field("filter") filter: String,
|
||||
@Query("username") username: String,
|
||||
@Query("password") password: String): Call<SuccessResponse>
|
||||
}
|
||||
// codebeat:disable[ARITY]
|
@ -6,12 +6,19 @@ import android.preference.PreferenceActivity;
|
||||
import android.support.annotation.LayoutRes;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.AppBarLayout;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AppCompatDelegate;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import apps.amine.bou.readerforselfoss.R;
|
||||
import com.ftinc.scoop.Scoop;
|
||||
|
||||
|
||||
/**
|
||||
* A {@link PreferenceActivity} which implements and proxies the necessary calls
|
||||
@ -25,12 +32,23 @@ public abstract class AppCompatPreferenceActivity extends PreferenceActivity {
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
getDelegate().installViewFactory();
|
||||
getDelegate().onCreate(savedInstanceState);
|
||||
Scoop.getInstance().apply(this);
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostCreate(Bundle savedInstanceState) {
|
||||
super.onPostCreate(savedInstanceState);
|
||||
|
||||
LinearLayout root = (LinearLayout)findViewById(android.R.id.list).getParent().getParent().getParent();
|
||||
AppBarLayout bar = (AppBarLayout) LayoutInflater.from(this).inflate(R.layout.settings_toolbar, root, false);
|
||||
Toolbar toolbar = bar.findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setDisplayShowHomeEnabled(true);
|
||||
|
||||
root.addView(bar, 0);
|
||||
|
||||
getDelegate().onPostCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
|
@ -2,24 +2,35 @@ package apps.amine.bou.readerforselfoss.settings;
|
||||
|
||||
|
||||
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.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;
|
||||
|
||||
|
||||
/**
|
||||
@ -118,6 +129,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
||||
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);
|
||||
}
|
||||
|
||||
@ -143,6 +155,61 @@ 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) { }
|
||||
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
|
||||
@ -162,18 +229,21 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
public static class LinksPreferenceFragment extends PreferenceFragment {
|
||||
public void openUrl(Uri uri) {
|
||||
Intent browserIntent = new Intent(Intent.ACTION_VIEW, uri);
|
||||
startActivity(browserIntent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
addPreferencesFromResource(R.xml.pref_links);
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
Preference tracker = findPreference( "trackerLink" );
|
||||
tracker.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
findPreference( "trackerLink" ).setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.tracker_url)));
|
||||
startActivity(browserIntent);
|
||||
openUrl(Uri.parse(BuildConfig.TRACKER_URL));
|
||||
return true;
|
||||
}
|
||||
});
|
||||
@ -181,8 +251,15 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
||||
findPreference("sourceLink").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.source_url)));
|
||||
startActivity(browserIntent);
|
||||
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;
|
||||
}
|
||||
});
|
||||
@ -199,6 +276,17 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHeaderClick(Header header, int position) {
|
||||
super.onHeaderClick(header, position);
|
||||
if (header.id == R.id.theme_change) {
|
||||
Intent intent = ScoopSettingsActivity.createIntent(getApplicationContext());
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
getApplicationContext().startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
|
@ -0,0 +1,51 @@
|
||||
package apps.amine.bou.readerforselfoss.themes
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.support.annotation.ColorInt
|
||||
import android.util.TypedValue
|
||||
import apps.amine.bou.readerforselfoss.R
|
||||
|
||||
|
||||
class AppColors(a: Activity) {
|
||||
@ColorInt val accent: Int
|
||||
@ColorInt val dark: Int
|
||||
@ColorInt val primary: Int
|
||||
@ColorInt val cardBackground: Int
|
||||
@ColorInt val windowBackground: Int
|
||||
val isDarkTheme: Boolean
|
||||
|
||||
init {
|
||||
val wrapper = Context::class.java
|
||||
val method = wrapper!!.getMethod("getThemeResId")
|
||||
method.isAccessible = true
|
||||
|
||||
isDarkTheme = when(method.invoke(a.baseContext)) {
|
||||
R.style.NoBarTealOrangeDark,
|
||||
R.style.NoBarDark,
|
||||
R.style.NoBarBlueAmberDark,
|
||||
R.style.NoBarGreyOrangeDark,
|
||||
R.style.NoBarIndigoPinkDark,
|
||||
R.style.NoBarRedTealDark,
|
||||
R.style.NoBarCyanPinkDark -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
val typedAccent = TypedValue()
|
||||
val typedAccentDark = TypedValue()
|
||||
val typedPrimary = TypedValue()
|
||||
val typedCardBackground = TypedValue()
|
||||
val typedWindowBackground = TypedValue()
|
||||
|
||||
a.theme.resolveAttribute(R.attr.colorAccent, typedAccent, true)
|
||||
a.theme.resolveAttribute(R.attr.colorAccent, typedAccent, true)
|
||||
a.theme.resolveAttribute(R.attr.colorPrimary, typedPrimary, true)
|
||||
a.theme.resolveAttribute(R.attr.cardBackgroundColor, typedCardBackground, true)
|
||||
a.theme.resolveAttribute(android.R.attr.colorBackground, typedWindowBackground, true)
|
||||
accent = typedAccent.data
|
||||
dark = typedAccentDark.data
|
||||
primary = typedPrimary.data
|
||||
cardBackground = typedCardBackground.data
|
||||
windowBackground = typedWindowBackground.data
|
||||
}
|
||||
}
|
@ -7,82 +7,120 @@ import android.net.Uri
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.text.TextUtils
|
||||
import android.util.Patterns
|
||||
import apps.amine.bou.readerforselfoss.BuildConfig
|
||||
import apps.amine.bou.readerforselfoss.R
|
||||
|
||||
import com.google.firebase.remoteconfig.FirebaseRemoteConfig
|
||||
import okhttp3.HttpUrl
|
||||
|
||||
private fun isStoreVersion(context: Context): Boolean {
|
||||
var result = false
|
||||
try {
|
||||
val installer = context.packageManager
|
||||
.getInstallerPackageName(context.packageName)
|
||||
result = !TextUtils.isEmpty(installer)
|
||||
} catch (e: Throwable) {
|
||||
}
|
||||
import apps.amine.bou.readerforselfoss.BuildConfig
|
||||
import apps.amine.bou.readerforselfoss.R
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.Item
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
fun checkAndDisplayStoreApk(context: Context) =
|
||||
if (!isStoreVersion(context) && !BuildConfig.GITHUB_VERSION) {
|
||||
val alertDialog = AlertDialog.Builder(context).create()
|
||||
alertDialog.setTitle(context.getString(R.string.warning_version))
|
||||
alertDialog.setMessage(context.getString(R.string.text_version))
|
||||
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() })
|
||||
{ dialog, _ -> dialog.dismiss() })
|
||||
alertDialog.show()
|
||||
} else Unit
|
||||
}
|
||||
|
||||
fun String.isUrlValid(): Boolean =
|
||||
HttpUrl.parse(this) != null && Patterns.WEB_URL.matcher(this).matches()
|
||||
|
||||
fun isUrlValid(url: String): Boolean {
|
||||
val baseUrl = HttpUrl.parse(url)
|
||||
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(url).matches() && existsAndEndsWithSlash
|
||||
return Patterns.WEB_URL.matcher(this).matches() && existsAndEndsWithSlash
|
||||
}
|
||||
|
||||
fun isEmptyOrNullOrNullString(str: String?): Boolean =
|
||||
str == null || str == "null" || str.isEmpty()
|
||||
fun String?.isEmptyOrNullOrNullString(): Boolean =
|
||||
this == null || this == "null" || this.isEmpty()
|
||||
|
||||
fun checkApkVersion(settings: SharedPreferences, editor: SharedPreferences.Editor, context: Context, mFirebaseRemoteConfig: FirebaseRemoteConfig) {
|
||||
mFirebaseRemoteConfig.fetch(43200)
|
||||
.addOnCompleteListener { task ->
|
||||
if (task.isSuccessful) {
|
||||
mFirebaseRemoteConfig.activateFetched()
|
||||
} else {
|
||||
fun Context.checkApkVersion(settings: SharedPreferences,
|
||||
editor: SharedPreferences.Editor,
|
||||
mFirebaseRemoteConfig: FirebaseRemoteConfig) = {
|
||||
fun isThereAnUpdate() {
|
||||
val APK_LINK = "github_apk"
|
||||
|
||||
val apkLink = mFirebaseRemoteConfig.getString(APK_LINK)
|
||||
val storedLink = settings.getString(APK_LINK, "")
|
||||
if (apkLink != storedLink && !apkLink.isEmpty()) {
|
||||
val alertDialog = AlertDialog.Builder(this).create()
|
||||
alertDialog.setTitle(getString(R.string.new_apk_available_title))
|
||||
alertDialog.setMessage(getString(R.string.new_apk_available_message))
|
||||
alertDialog.setButton(AlertDialog.BUTTON_POSITIVE, getString(R.string.new_apk_available_get)) { _, _ ->
|
||||
editor.putString(APK_LINK, apkLink)
|
||||
editor.apply()
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(apkLink))
|
||||
startActivity(browserIntent)
|
||||
}
|
||||
|
||||
isThereAnUpdate(settings, editor, context, mFirebaseRemoteConfig)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isThereAnUpdate(settings: SharedPreferences, editor: SharedPreferences.Editor, context: Context, mFirebaseRemoteConfig: FirebaseRemoteConfig) {
|
||||
val APK_LINK = "github_apk"
|
||||
|
||||
val apkLink = mFirebaseRemoteConfig.getString(APK_LINK)
|
||||
val storedLink = settings.getString(APK_LINK, "")
|
||||
if (apkLink != storedLink && !apkLink.isEmpty()) {
|
||||
val alertDialog = AlertDialog.Builder(context).create()
|
||||
alertDialog.setTitle(context.getString(R.string.new_apk_available_title))
|
||||
alertDialog.setMessage(context.getString(R.string.new_apk_available_message))
|
||||
alertDialog.setButton(AlertDialog.BUTTON_POSITIVE, context.getString(R.string.new_apk_available_get)) { _, _ ->
|
||||
editor.putString(APK_LINK, apkLink)
|
||||
editor.apply()
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(apkLink))
|
||||
context.startActivity(browserIntent)
|
||||
}
|
||||
alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, context.getString(R.string.new_apk_available_no),
|
||||
alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, getString(R.string.new_apk_available_no),
|
||||
{ dialog, _ ->
|
||||
editor.putString(APK_LINK, apkLink)
|
||||
editor.apply()
|
||||
dialog.dismiss()
|
||||
})
|
||||
alertDialog.show()
|
||||
alertDialog.show()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
mFirebaseRemoteConfig.fetch(43200)
|
||||
.addOnCompleteListener { task ->
|
||||
if (task.isSuccessful) {
|
||||
mFirebaseRemoteConfig.activateFetched()
|
||||
}
|
||||
|
||||
isThereAnUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
fun String.longHash(): Long {
|
||||
var h = 98764321261L
|
||||
val l = this.length
|
||||
val chars = this.toCharArray()
|
||||
|
||||
for (i in 0 until l) {
|
||||
h = 31 * h + chars[i].toLong()
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
fun String.toStringUriWithHttp() =
|
||||
if (!this.startsWith("https://") && !this.startsWith("http://"))
|
||||
"http://" + this
|
||||
else
|
||||
this
|
||||
|
||||
fun Context.shareLink(itemUrl: String) {
|
||||
val sendIntent = Intent()
|
||||
sendIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
sendIntent.action = Intent.ACTION_SEND
|
||||
sendIntent.putExtra(Intent.EXTRA_TEXT, itemUrl.toStringUriWithHttp())
|
||||
sendIntent.type = "text/plain"
|
||||
startActivity(Intent.createChooser(sendIntent, getString(R.string.share)).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
|
||||
}
|
||||
|
||||
fun Context.openInBrowser(i: Item) {
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
intent.data = Uri.parse(i.getLinkDecoded().toStringUriWithHttp())
|
||||
startActivity(intent)
|
||||
}
|
@ -1,16 +1,15 @@
|
||||
package apps.amine.bou.readerforselfoss.utils
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import apps.amine.bou.readerforselfoss.LoginActivity
|
||||
|
||||
|
||||
class Config(c: Context) {
|
||||
|
||||
private val settings: SharedPreferences
|
||||
|
||||
init {
|
||||
this.settings = c.getSharedPreferences(settingsName, Context.MODE_PRIVATE)
|
||||
}
|
||||
val settings: SharedPreferences = c.getSharedPreferences(settingsName, Context.MODE_PRIVATE)
|
||||
|
||||
val baseUrl: String
|
||||
get() = settings.getString("url", "")
|
||||
@ -27,8 +26,24 @@ class Config(c: Context) {
|
||||
val httpUserPassword: String
|
||||
get() = settings.getString("httpPassword", "")
|
||||
|
||||
|
||||
companion object {
|
||||
val settingsName = "paramsselfoss"
|
||||
|
||||
fun logoutAndRedirect(c: Context,
|
||||
callingActivity: Activity,
|
||||
editor: SharedPreferences.Editor,
|
||||
baseUrlFail: Boolean = false): Boolean {
|
||||
editor.remove("url")
|
||||
editor.remove("login")
|
||||
editor.remove("password")
|
||||
editor.apply()
|
||||
val intent = Intent(c, LoginActivity::class.java)
|
||||
if (baseUrlFail)
|
||||
intent.putExtra("baseUrlFail", baseUrlFail)
|
||||
c.startActivity(intent)
|
||||
callingActivity.finish()
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package apps.amine.bou.readerforselfoss.utils
|
||||
|
||||
import android.text.format.DateUtils
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.Item
|
||||
import java.text.ParseException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
|
||||
fun String.toTextDrawableString(): String {
|
||||
val textDrawable = StringBuilder()
|
||||
for (s in this.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) {
|
||||
textDrawable.append(s[0])
|
||||
}
|
||||
return textDrawable.toString()
|
||||
}
|
||||
|
||||
fun Item.sourceAndDateText(): String {
|
||||
val formattedDate: String = try {
|
||||
" " + DateUtils.getRelativeTimeSpanString(
|
||||
SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(this.datetime).time,
|
||||
Date().time,
|
||||
DateUtils.MINUTE_IN_MILLIS,
|
||||
DateUtils.FORMAT_ABBREV_RELATIVE
|
||||
)
|
||||
} catch (e: ParseException) {
|
||||
e.printStackTrace()
|
||||
""
|
||||
}
|
||||
|
||||
return this.sourcetitle + formattedDate
|
||||
}
|
@ -9,73 +9,78 @@ import android.net.Uri
|
||||
import android.support.customtabs.CustomTabsIntent
|
||||
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 xyz.klinker.android.drag_dismiss.DragDismissIntentBuilder
|
||||
|
||||
fun buildCustomTabsIntent(c: Context): CustomTabsIntent {
|
||||
|
||||
fun createPendingShareIntent(c: Context): PendingIntent {
|
||||
val actionIntent = Intent(Intent.ACTION_SEND)
|
||||
actionIntent.type = "text/plain"
|
||||
return PendingIntent.getActivity(
|
||||
c, 0, actionIntent, 0)
|
||||
}
|
||||
fun Context.buildCustomTabsIntent(): CustomTabsIntent {
|
||||
|
||||
val actionIntent = Intent(Intent.ACTION_SEND)
|
||||
actionIntent.type = "text/plain"
|
||||
val createPendingShareIntent: PendingIntent = PendingIntent.getActivity(this, 0, actionIntent, 0)
|
||||
|
||||
|
||||
val intentBuilder = CustomTabsIntent.Builder()
|
||||
|
||||
// TODO: change to primary when it's possible to customize custom tabs title color
|
||||
//intentBuilder.setToolbarColor(c.getResources().getColor(R.color.colorPrimary));
|
||||
intentBuilder.setToolbarColor(c.resources.getColor(R.color.colorAccentDark))
|
||||
intentBuilder.setToolbarColor(resources.getColor(R.color.colorAccentDark))
|
||||
intentBuilder.setShowTitle(true)
|
||||
|
||||
|
||||
intentBuilder.setStartAnimations(c,
|
||||
intentBuilder.setStartAnimations(this,
|
||||
R.anim.slide_in_right,
|
||||
R.anim.slide_out_left)
|
||||
intentBuilder.setExitAnimations(c,
|
||||
intentBuilder.setExitAnimations(this,
|
||||
android.R.anim.slide_in_left,
|
||||
android.R.anim.slide_out_right)
|
||||
|
||||
val closeicon = BitmapFactory.decodeResource(c.resources, R.drawable.ic_close_white_24dp)
|
||||
val closeicon = BitmapFactory.decodeResource(resources, R.drawable.ic_close_white_24dp)
|
||||
intentBuilder.setCloseButtonIcon(closeicon)
|
||||
|
||||
val shareLabel = c.getString(R.string.label_share)
|
||||
val icon = BitmapFactory.decodeResource(c.resources,
|
||||
val shareLabel = this.getString(R.string.label_share)
|
||||
val icon = BitmapFactory.decodeResource(resources,
|
||||
R.drawable.ic_share_white_24dp)
|
||||
intentBuilder.setActionButton(icon, shareLabel, createPendingShareIntent(c))
|
||||
intentBuilder.setActionButton(icon, shareLabel, createPendingShareIntent)
|
||||
|
||||
return intentBuilder.build()
|
||||
}
|
||||
|
||||
fun openItemUrl(i: Item,
|
||||
customTabsIntent: CustomTabsIntent,
|
||||
internalBrowser: Boolean,
|
||||
articleViewer: Boolean,
|
||||
app: Activity,
|
||||
c: Context) {
|
||||
if (!internalBrowser) {
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.data = Uri.parse(i.getLinkDecoded())
|
||||
app.startActivity(intent)
|
||||
fun Context.openItemUrl(linkDecoded: String,
|
||||
customTabsIntent: CustomTabsIntent,
|
||||
internalBrowser: Boolean,
|
||||
articleViewer: Boolean,
|
||||
app: Activity) {
|
||||
if (!internalBrowser || !linkDecoded.isUrlValid()) {
|
||||
openInBrowser(linkDecoded, app)
|
||||
} else {
|
||||
if (articleViewer) {
|
||||
val intent = Intent(c, ReaderActivity::class.java)
|
||||
val intent = Intent(this, ReaderActivity::class.java)
|
||||
|
||||
DragDismissIntentBuilder(c)
|
||||
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())
|
||||
intent.putExtra("url", linkDecoded)
|
||||
app.startActivity(intent)
|
||||
} else {
|
||||
CustomTabActivityHelper.openCustomTab(app, customTabsIntent, Uri.parse(i.getLinkDecoded())
|
||||
) { _, uri ->
|
||||
val intent = Intent(Intent.ACTION_VIEW, uri)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
c.startActivity(intent)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun openInBrowser(linkDecoded: String, app: Activity) {
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.data = Uri.parse(linkDecoded)
|
||||
app.startActivity(intent)
|
||||
}
|
||||
|
@ -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,7 +1,6 @@
|
||||
package apps.amine.bou.readerforselfoss.utils.customtabs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ComponentName;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.customtabs.CustomTabsClient;
|
||||
@ -11,28 +10,30 @@ import android.support.customtabs.CustomTabsSession;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("ALL")
|
||||
public class CustomTabActivityHelper {
|
||||
/**
|
||||
* This is a helper class to manage the connection to the Custom Tabs Service.
|
||||
*/
|
||||
public class CustomTabActivityHelper implements ServiceConnectionCallback {
|
||||
private CustomTabsSession mCustomTabsSession;
|
||||
private CustomTabsClient mClient;
|
||||
private CustomTabsServiceConnection mConnection;
|
||||
private ConnectionCallback mConnectionCallback;
|
||||
|
||||
/**
|
||||
* Opens the URL on a Custom Tab if possible. Otherwise fallsback to opening it on a WebView
|
||||
* Opens the URL on a Custom Tab if possible. Otherwise fallsback to opening it on a WebView.
|
||||
*
|
||||
* @param activity The host activity
|
||||
* @param customTabsIntent a CustomTabsIntent to be used if Custom Tabs is available
|
||||
* @param uri the Uri to be opened
|
||||
* @param fallback a CustomTabFallback to be used if Custom Tabs is not available
|
||||
* @param activity The host activity.
|
||||
* @param customTabsIntent a CustomTabsIntent to be used if Custom Tabs is available.
|
||||
* @param uri the Uri to be opened.
|
||||
* @param fallback a CustomTabFallback to be used if Custom Tabs is not available.
|
||||
*/
|
||||
public static void openCustomTab(Activity activity,
|
||||
CustomTabsIntent customTabsIntent,
|
||||
Uri uri,
|
||||
CustomTabFallback fallback) {
|
||||
CustomTabsIntent customTabsIntent,
|
||||
Uri uri,
|
||||
CustomTabFallback fallback) {
|
||||
String packageName = CustomTabsHelper.getPackageNameToUse(activity);
|
||||
|
||||
//If we cant find a package name, it means there's no browser that supports
|
||||
//If we cant find a package name, it means theres no browser that supports
|
||||
//Chrome Custom Tabs installed. So, we fallback to the webview
|
||||
if (packageName == null) {
|
||||
if (fallback != null) {
|
||||
@ -45,22 +46,21 @@ public class CustomTabActivityHelper {
|
||||
}
|
||||
|
||||
/**
|
||||
* Unbinds the Activity from the Custom Tabs Service
|
||||
* @param activity the activity that is connected to the service
|
||||
* Unbinds the Activity from the Custom Tabs Service.
|
||||
* @param activity the activity that is connected to the service.
|
||||
*/
|
||||
public void unbindCustomTabsService(Activity activity) {
|
||||
try {
|
||||
if (mConnection == null) return;
|
||||
activity.unbindService(mConnection);
|
||||
mClient = null;
|
||||
mCustomTabsSession = null;
|
||||
} catch (RuntimeException e) {}
|
||||
if (mConnection == null) return;
|
||||
activity.unbindService(mConnection);
|
||||
mClient = null;
|
||||
mCustomTabsSession = null;
|
||||
mConnection = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates or retrieves an exiting CustomTabsSession
|
||||
* Creates or retrieves an exiting CustomTabsSession.
|
||||
*
|
||||
* @return a CustomTabsSession
|
||||
* @return a CustomTabsSession.
|
||||
*/
|
||||
public CustomTabsSession getSession() {
|
||||
if (mClient == null) {
|
||||
@ -72,7 +72,7 @@ public class CustomTabActivityHelper {
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a Callback to be called when connected or disconnected from the Custom Tabs Service
|
||||
* Register a Callback to be called when connected or disconnected from the Custom Tabs Service.
|
||||
* @param connectionCallback
|
||||
*/
|
||||
public void setConnectionCallback(ConnectionCallback connectionCallback) {
|
||||
@ -80,67 +80,72 @@ public class CustomTabActivityHelper {
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds the Activity to the Custom Tabs Service
|
||||
* @param activity the activity to be binded to the service
|
||||
* Binds the Activity to the Custom Tabs Service.
|
||||
* @param activity the activity to be binded to the service.
|
||||
*/
|
||||
public void bindCustomTabsService(Activity activity) {
|
||||
if (mClient != null) return;
|
||||
|
||||
String packageName = CustomTabsHelper.getPackageNameToUse(activity);
|
||||
if (packageName == null) return;
|
||||
mConnection = new CustomTabsServiceConnection() {
|
||||
@Override
|
||||
public void onCustomTabsServiceConnected(ComponentName name, CustomTabsClient client) {
|
||||
mClient = client;
|
||||
mClient.warmup(0L);
|
||||
if (mConnectionCallback != null) mConnectionCallback.onCustomTabsConnected();
|
||||
//Initialize a session as soon as possible.
|
||||
getSession();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
mClient = null;
|
||||
if (mConnectionCallback != null) mConnectionCallback.onCustomTabsDisconnected();
|
||||
}
|
||||
};
|
||||
mConnection = new ServiceConnection(this);
|
||||
CustomTabsClient.bindCustomTabsService(activity, packageName, mConnection);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see {@link CustomTabsSession#mayLaunchUrl(Uri, Bundle, List)}.
|
||||
* @return true if call to mayLaunchUrl was accepted.
|
||||
*/
|
||||
public boolean mayLaunchUrl(Uri uri, Bundle extras, List<Bundle> otherLikelyBundles) {
|
||||
if (mClient == null) return false;
|
||||
|
||||
CustomTabsSession session = getSession();
|
||||
return session != null && session.mayLaunchUrl(uri, extras, otherLikelyBundles);
|
||||
if (session == null) return false;
|
||||
|
||||
return session.mayLaunchUrl(uri, extras, otherLikelyBundles);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceConnected(CustomTabsClient client) {
|
||||
mClient = client;
|
||||
mClient.warmup(0L);
|
||||
if (mConnectionCallback != null) mConnectionCallback.onCustomTabsConnected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected() {
|
||||
mClient = null;
|
||||
mCustomTabsSession = null;
|
||||
if (mConnectionCallback != null) mConnectionCallback.onCustomTabsDisconnected();
|
||||
}
|
||||
|
||||
/**
|
||||
* A Callback for when the service is connected or disconnected. Use those callbacks to
|
||||
* handle UI changes when the service is connected or disconnected
|
||||
* handle UI changes when the service is connected or disconnected.
|
||||
*/
|
||||
public interface ConnectionCallback {
|
||||
/**
|
||||
* Called when the service is connected
|
||||
* Called when the service is connected.
|
||||
*/
|
||||
void onCustomTabsConnected();
|
||||
|
||||
/**
|
||||
* Called when the service is disconnected
|
||||
* Called when the service is disconnected.
|
||||
*/
|
||||
void onCustomTabsDisconnected();
|
||||
}
|
||||
|
||||
/**
|
||||
* To be used as a fallback to open the Uri when Custom Tabs is not available
|
||||
* To be used as a fallback to open the Uri when Custom Tabs is not available.
|
||||
*/
|
||||
public interface CustomTabFallback {
|
||||
/**
|
||||
*
|
||||
* @param activity The Activity that wants to open the Uri
|
||||
* @param uri The uri to be opened by the fallback
|
||||
* @param activity The Activity that wants to open the Uri.
|
||||
* @param uri The uri to be opened by the fallback.
|
||||
*/
|
||||
void openUri(Activity activity, Uri uri);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package apps.amine.bou.readerforselfoss.utils.customtabs;
|
||||
|
||||
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
|
||||
*/
|
||||
public class ServiceConnection extends CustomTabsServiceConnection {
|
||||
// A weak reference to the ServiceConnectionCallback to avoid leaking it.
|
||||
private WeakReference<ServiceConnectionCallback> mConnectionCallback;
|
||||
|
||||
public ServiceConnection(ServiceConnectionCallback connectionCallback) {
|
||||
mConnectionCallback = new WeakReference<>(connectionCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCustomTabsServiceConnected(ComponentName name, CustomTabsClient client) {
|
||||
ServiceConnectionCallback connectionCallback = mConnectionCallback.get();
|
||||
if (connectionCallback != null) connectionCallback.onServiceConnected(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
ServiceConnectionCallback connectionCallback = mConnectionCallback.get();
|
||||
if (connectionCallback != null) connectionCallback.onServiceDisconnected();
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package apps.amine.bou.readerforselfoss.utils.customtabs;
|
||||
|
||||
|
||||
import android.support.customtabs.CustomTabsClient;
|
||||
|
||||
|
||||
public interface ServiceConnectionCallback {
|
||||
/**
|
||||
* Called when the service is connected.
|
||||
* @param client a CustomTabsClient
|
||||
*/
|
||||
void onServiceConnected(CustomTabsClient client);
|
||||
|
||||
/**
|
||||
* Called when the service is disconnected.
|
||||
*/
|
||||
void onServiceDisconnected();
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
/* From https://github.com/mikepenz/MaterialDrawer/blob/develop/app/src/main/java/com/mikepenz/materialdrawer/app/drawerItems/CustomBaseViewHolder.java */
|
||||
package apps.amine.bou.readerforselfoss.utils.drawer
|
||||
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
|
||||
import apps.amine.bou.readerforselfoss.R
|
||||
|
||||
|
||||
|
||||
open class CustomBaseViewHolder(var view: View) : RecyclerView.ViewHolder(view) {
|
||||
var icon: ImageView = view.findViewById(R.id.material_drawer_icon)
|
||||
var name: TextView = view.findViewById(R.id.material_drawer_name)
|
||||
var description: TextView = view.findViewById(R.id.material_drawer_description)
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
/* From https://github.com/mikepenz/MaterialDrawer/blob/develop/app/src/main/java/com/mikepenz/materialdrawer/app/drawerItems/CustomUrlBasePrimaryDrawerItem.java */
|
||||
package apps.amine.bou.readerforselfoss.utils.drawer
|
||||
|
||||
import android.net.Uri
|
||||
import android.support.annotation.ColorInt
|
||||
import android.support.annotation.ColorRes
|
||||
import android.support.annotation.StringRes
|
||||
import android.support.v7.widget.RecyclerView
|
||||
|
||||
import com.mikepenz.materialdrawer.holder.ColorHolder
|
||||
import com.mikepenz.materialdrawer.holder.ImageHolder
|
||||
import com.mikepenz.materialdrawer.holder.StringHolder
|
||||
import com.mikepenz.materialdrawer.model.BaseDrawerItem
|
||||
import com.mikepenz.materialdrawer.util.DrawerImageLoader
|
||||
import com.mikepenz.materialdrawer.util.DrawerUIUtils
|
||||
import com.mikepenz.materialize.util.UIUtils
|
||||
|
||||
|
||||
|
||||
abstract class CustomUrlBasePrimaryDrawerItem<T, VH : RecyclerView.ViewHolder> : BaseDrawerItem<T, VH>() {
|
||||
fun withIcon(url: String): T {
|
||||
this.icon = ImageHolder(url)
|
||||
return this as T
|
||||
}
|
||||
|
||||
fun withIcon(uri: Uri): T {
|
||||
this.icon = ImageHolder(uri)
|
||||
return this as T
|
||||
}
|
||||
|
||||
var description: StringHolder? = null
|
||||
private set
|
||||
var descriptionTextColor: ColorHolder? = null
|
||||
private set
|
||||
|
||||
fun withDescription(description: String): T {
|
||||
this.description = StringHolder(description)
|
||||
return this as T
|
||||
}
|
||||
|
||||
fun withDescription(@StringRes descriptionRes: Int): T {
|
||||
this.description = StringHolder(descriptionRes)
|
||||
return this as T
|
||||
}
|
||||
|
||||
fun withDescriptionTextColor(@ColorInt color: Int): T {
|
||||
this.descriptionTextColor = ColorHolder.fromColor(color)
|
||||
return this as T
|
||||
}
|
||||
|
||||
fun withDescriptionTextColorRes(@ColorRes colorRes: Int): T {
|
||||
this.descriptionTextColor = ColorHolder.fromColorRes(colorRes)
|
||||
return this as T
|
||||
}
|
||||
|
||||
/**
|
||||
* a helper method to have the logic for all secondaryDrawerItems only once
|
||||
|
||||
* @param viewHolder
|
||||
*/
|
||||
protected fun bindViewHelper(viewHolder: CustomBaseViewHolder) {
|
||||
val ctx = viewHolder.itemView.context
|
||||
|
||||
//set the identifier from the drawerItem here. It can be used to run tests
|
||||
viewHolder.itemView.id = hashCode()
|
||||
|
||||
//set the item selected if it is
|
||||
viewHolder.itemView.isSelected = isSelected
|
||||
|
||||
//get the correct color for the background
|
||||
val selectedColor = getSelectedColor(ctx)
|
||||
//get the correct color for the text
|
||||
val color = getColor(ctx)
|
||||
val selectedTextColor = getSelectedTextColor(ctx)
|
||||
//get the correct color for the icon
|
||||
val iconColor = getIconColor(ctx)
|
||||
val selectedIconColor = getSelectedIconColor(ctx)
|
||||
|
||||
//set the background for the item
|
||||
UIUtils.setBackground(viewHolder.view, UIUtils.getSelectableBackground(ctx, selectedColor, true))
|
||||
//set the text for the name
|
||||
StringHolder.applyTo(this.getName(), viewHolder.name)
|
||||
//set the text for the description or hide
|
||||
StringHolder.applyToOrHide(this.description, viewHolder.description)
|
||||
|
||||
//set the colors for textViews
|
||||
viewHolder.name.setTextColor(getTextColorStateList(color, selectedTextColor))
|
||||
//set the description text color
|
||||
ColorHolder.applyToOr(descriptionTextColor,
|
||||
viewHolder.description, getTextColorStateList(color, selectedTextColor))
|
||||
|
||||
//define the typeface for our textViews
|
||||
if (getTypeface() != null) {
|
||||
viewHolder.name.typeface = getTypeface()
|
||||
viewHolder.description.typeface = getTypeface()
|
||||
}
|
||||
|
||||
//we make sure we reset the image first before setting the new one in case there is an empty one
|
||||
DrawerImageLoader.getInstance().cancelImage(viewHolder.icon)
|
||||
viewHolder.icon.setImageBitmap(null)
|
||||
//get the drawables for our icon and set it
|
||||
ImageHolder.applyTo(icon, viewHolder.icon, "customUrlItem")
|
||||
|
||||
//for android API 17 --> Padding not applied via xml
|
||||
DrawerUIUtils.setDrawerVerticalPadding(viewHolder.view)
|
||||
}
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
/* From https://github.com/mikepenz/MaterialDrawer/blob/develop/app/src/main/java/com/mikepenz/materialdrawer/app/drawerItems/CustomUrlPrimaryDrawerItem.java */
|
||||
package apps.amine.bou.readerforselfoss.utils.drawer
|
||||
|
||||
import android.support.annotation.LayoutRes
|
||||
import android.support.annotation.StringRes
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
|
||||
import com.mikepenz.materialdrawer.holder.BadgeStyle
|
||||
import com.mikepenz.materialdrawer.holder.StringHolder
|
||||
import com.mikepenz.materialdrawer.model.interfaces.ColorfulBadgeable
|
||||
|
||||
import apps.amine.bou.readerforselfoss.R
|
||||
|
||||
|
||||
|
||||
class CustomUrlPrimaryDrawerItem : CustomUrlBasePrimaryDrawerItem<CustomUrlPrimaryDrawerItem, CustomUrlPrimaryDrawerItem.ViewHolder>(), ColorfulBadgeable<CustomUrlPrimaryDrawerItem> {
|
||||
protected var mBadge: StringHolder = StringHolder("")
|
||||
protected var mBadgeStyle = BadgeStyle()
|
||||
|
||||
override fun withBadge(badge: StringHolder): CustomUrlPrimaryDrawerItem {
|
||||
this.mBadge = badge
|
||||
return this
|
||||
}
|
||||
|
||||
override fun withBadge(badge: String): CustomUrlPrimaryDrawerItem {
|
||||
this.mBadge = StringHolder(badge)
|
||||
return this
|
||||
}
|
||||
|
||||
override fun withBadge(@StringRes badgeRes: Int): CustomUrlPrimaryDrawerItem {
|
||||
this.mBadge = StringHolder(badgeRes)
|
||||
return this
|
||||
}
|
||||
|
||||
override fun withBadgeStyle(badgeStyle: BadgeStyle): CustomUrlPrimaryDrawerItem {
|
||||
this.mBadgeStyle = badgeStyle
|
||||
return this
|
||||
}
|
||||
|
||||
override fun getBadge(): StringHolder {
|
||||
return mBadge
|
||||
}
|
||||
|
||||
override fun getBadgeStyle(): BadgeStyle {
|
||||
return mBadgeStyle
|
||||
}
|
||||
|
||||
override fun getType(): Int {
|
||||
return R.id.material_drawer_item_custom_url_item
|
||||
}
|
||||
|
||||
@LayoutRes
|
||||
override fun getLayoutRes(): Int {
|
||||
return R.layout.material_drawer_item_primary
|
||||
}
|
||||
|
||||
override fun bindView(viewHolder: ViewHolder, payloads: List<*>?) {
|
||||
super.bindView(viewHolder, payloads)
|
||||
|
||||
val ctx = viewHolder.itemView.context
|
||||
|
||||
//bind the basic view parts
|
||||
bindViewHelper(viewHolder)
|
||||
|
||||
//set the text for the badge or hide
|
||||
val badgeVisible = StringHolder.applyToOrHide(mBadge, viewHolder.badge)
|
||||
//style the badge if it is visible
|
||||
if (badgeVisible) {
|
||||
mBadgeStyle.style(viewHolder.badge, getTextColorStateList(getColor(ctx), getSelectedTextColor(ctx)))
|
||||
viewHolder.badgeContainer.visibility = View.VISIBLE
|
||||
} else {
|
||||
viewHolder.badgeContainer.visibility = View.GONE
|
||||
}
|
||||
|
||||
//define the typeface for our textViews
|
||||
if (getTypeface() != null) {
|
||||
viewHolder.badge.typeface = getTypeface()
|
||||
}
|
||||
|
||||
//call the onPostBindView method to trigger post bind view actions (like the listener to modify the item if required)
|
||||
onPostBindView(this, viewHolder.itemView)
|
||||
}
|
||||
|
||||
override fun getViewHolder(v: View): ViewHolder {
|
||||
return ViewHolder(v)
|
||||
}
|
||||
|
||||
class ViewHolder(view: View) : CustomBaseViewHolder(view) {
|
||||
val badgeContainer: View = view.findViewById(R.id.material_drawer_badge_container)
|
||||
val badge: TextView = view.findViewById(R.id.material_drawer_badge)
|
||||
|
||||
}
|
||||
}
|
@ -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.module.GlideModule
|
||||
import com.bumptech.glide.load.model.GlideUrl
|
||||
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))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
BIN
app/src/main/res/drawable-hdpi/ic_action_search.png
Normal file
After Width: | Height: | Size: 680 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_color_lens_black_24dp.png
Normal file
After Width: | Height: | Size: 458 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_settings.png
Normal file
After Width: | Height: | Size: 498 B |
BIN
app/src/main/res/drawable-mdpi/ic_action_search.png
Normal file
After Width: | Height: | Size: 442 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_color_lens_black_24dp.png
Normal file
After Width: | Height: | Size: 268 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_settings.png
Normal file
After Width: | Height: | Size: 339 B |
BIN
app/src/main/res/drawable-xhdpi/ic_action_search.png
Normal file
After Width: | Height: | Size: 634 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_color_lens_black_24dp.png
Normal file
After Width: | Height: | Size: 504 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_settings.png
Normal file
After Width: | Height: | Size: 606 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_action_search.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
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_color_lens_black_24dp.png
Normal file
After Width: | Height: | Size: 741 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_settings.png
Normal file
After Width: | Height: | Size: 907 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_action_search.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
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_color_lens_black_24dp.png
Normal file
After Width: | Height: | Size: 966 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_settings.png
Normal file
After Width: | Height: | Size: 1.2 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 |
@ -1,74 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
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.HomeActivity">
|
||||
|
||||
<com.roughike.bottombar.BottomBar
|
||||
android:id="@+id/bottomBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
app:bb_tabletMode="true"
|
||||
app:bb_tabXmlResource="@xml/bottombar"
|
||||
app:bb_activeTabColor="@color/white"
|
||||
app:bb_inActiveTabColor="@color/black"
|
||||
app:bb_badgeBackgroundColor="@color/colorPrimary"/>
|
||||
|
||||
<com.github.stkent.amplify.prompt.DefaultLayoutPromptView
|
||||
android:id="@+id/prompt_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:prompt_view_user_opinion_question_title="@string/rating_prompt_title"
|
||||
app:prompt_view_user_opinion_question_positive_button_label="@string/rating_prompt_yes"
|
||||
app:prompt_view_user_opinion_question_negative_button_label="@string/rating_prompt_no"
|
||||
app:prompt_view_positive_feedback_question_title="@string/rating_prompt_rating_title"
|
||||
app:prompt_view_positive_feedback_question_positive_button_label="@string/rating_prompt_rating_yes"
|
||||
app:prompt_view_positive_feedback_question_negative_button_label="@string/rating_prompt_rating_no"
|
||||
app:prompt_view_critical_feedback_question_title="@string/rating_prompt_feedback_title"
|
||||
app:prompt_view_critical_feedback_question_positive_button_label="@string/rating_prompt_feedback_yes"
|
||||
app:prompt_view_critical_feedback_question_negative_button_label="@string/rating_prompt_feedback_no"
|
||||
app:prompt_view_thanks_title="@string/rating_prompt_thanks"
|
||||
app:prompt_view_positive_button_background_color="@color/colorPrimary"
|
||||
app:prompt_view_positive_button_text_color="@color/white"
|
||||
app:prompt_view_positive_button_border_color="@color/colorPrimary"
|
||||
app:prompt_view_negative_button_background_color="@color/colorAccent"
|
||||
app:prompt_view_negative_button_border_color="@color/white"
|
||||
app:prompt_view_thanks_display_time_ms="2000"
|
||||
android:layout_toEndOf="@+id/bottomBar"
|
||||
android:layout_toRightOf="@+id/bottomBar"/>
|
||||
|
||||
|
||||
<!-- This could be your fragment container, or something -->
|
||||
<android.support.design.widget.CoordinatorLayout
|
||||
android:id="@+id/coordLayout"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_toEndOf="@+id/bottomBar"
|
||||
android:layout_toRightOf="@+id/bottomBar"
|
||||
android:layout_below="@id/prompt_view">
|
||||
|
||||
<android.support.v4.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swipeRefreshLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/my_recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/background_grey"
|
||||
android:scrollbars="vertical"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||
</android.support.v4.widget.SwipeRefreshLayout>
|
||||
|
||||
</android.support.design.widget.CoordinatorLayout>
|
||||
|
||||
|
||||
</RelativeLayout>
|
@ -1,118 +1,136 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.constraint.ConstraintLayout
|
||||
<LinearLayout
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
tools:context="apps.amine.bou.readerforselfoss.AddSourceActivity">
|
||||
tools:context="apps.amine.bou.readerforselfoss.AddSourceActivity"
|
||||
android:orientation="vertical">
|
||||
<android.support.design.widget.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:theme="@style/ToolBarStyle" />
|
||||
|
||||
</android.support.design.widget.AppBarLayout>
|
||||
|
||||
|
||||
<android.support.constraint.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:id="@+id/formContainer"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintHorizontal_bias="1.0"
|
||||
app:layout_constraintVertical_bias="0.0">
|
||||
|
||||
<TextView
|
||||
android:text="@string/add_source"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/textView2"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Large"
|
||||
android:textAlignment="center"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_width="match_parent">
|
||||
<android.support.constraint.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:layout_marginEnd="16dp"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginStart="16dp"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
android:layout_marginLeft="16dp"/>
|
||||
|
||||
<EditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ems="10"
|
||||
android:id="@+id/nameInput"
|
||||
android:layout_marginTop="32dp"
|
||||
app:layout_constraintTop_toBottomOf="@+id/textView2"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
android:inputType="text"
|
||||
android:hint="@string/add_source_hint_name"/>
|
||||
|
||||
<EditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textUri"
|
||||
android:ems="10"
|
||||
android:id="@+id/sourceUri"
|
||||
android:hint="@string/add_source_hint_url"
|
||||
android:layout_marginTop="16dp"
|
||||
app:layout_constraintTop_toBottomOf="@+id/nameInput"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"/>
|
||||
|
||||
<EditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ems="10"
|
||||
android:id="@+id/tags"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
android:layout_marginTop="16dp"
|
||||
app:layout_constraintTop_toBottomOf="@+id/sourceUri"
|
||||
android:hint="@string/add_source_hint_tags"
|
||||
android:inputType="text"/>
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:id="@+id/formContainer"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintHorizontal_bias="1.0"
|
||||
app:layout_constraintVertical_bias="0.0">
|
||||
|
||||
<Spinner
|
||||
android:layout_width="match_parent"
|
||||
android:id="@+id/spoutsSpinner"
|
||||
android:layout_marginTop="16dp"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tags"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
android:layout_height="40dp"/>
|
||||
<TextView
|
||||
android:text="@string/add_source"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/textView2"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Large"
|
||||
android:textAlignment="center"
|
||||
android:layout_marginTop="16dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:layout_marginEnd="16dp"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginStart="16dp"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
android:layout_marginLeft="16dp"/>
|
||||
|
||||
<Button
|
||||
android:text="@string/add_source_save"
|
||||
<EditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ems="10"
|
||||
android:id="@+id/nameInput"
|
||||
android:layout_marginTop="32dp"
|
||||
app:layout_constraintTop_toBottomOf="@+id/textView2"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
android:inputType="text"
|
||||
android:hint="@string/add_source_hint_name"/>
|
||||
|
||||
<EditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textUri"
|
||||
android:ems="10"
|
||||
android:id="@+id/sourceUri"
|
||||
android:hint="@string/add_source_hint_url"
|
||||
android:layout_marginTop="16dp"
|
||||
app:layout_constraintTop_toBottomOf="@+id/nameInput"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"/>
|
||||
|
||||
<EditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ems="10"
|
||||
android:id="@+id/tags"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
android:layout_marginTop="16dp"
|
||||
app:layout_constraintTop_toBottomOf="@+id/sourceUri"
|
||||
android:hint="@string/add_source_hint_tags"
|
||||
android:inputType="text"/>
|
||||
|
||||
<Spinner
|
||||
android:layout_width="match_parent"
|
||||
android:id="@+id/spoutsSpinner"
|
||||
android:layout_marginTop="16dp"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tags"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
android:layout_height="40dp"/>
|
||||
|
||||
<Button
|
||||
android:text="@string/add_source_save"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/saveBtn"
|
||||
android:elevation="5dp"
|
||||
android:textColor="?attr/colorAccent"
|
||||
android:layout_marginEnd="16dp"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginStart="16dp"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
app:layout_constraintTop_toBottomOf="@+id/spoutsSpinner"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:layout_constraintVertical_bias="0.0"/>
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
|
||||
<ProgressBar
|
||||
style="?android:attr/progressBarStyleLarge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/saveBtn"
|
||||
android:elevation="5dp"
|
||||
android:textColor="@color/colorAccent"
|
||||
android:layout_marginEnd="16dp"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:id="@+id/progress"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
app:layout_constraintTop_toBottomOf="@+id/spoutsSpinner"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:layout_constraintVertical_bias="0.0"/>
|
||||
android:visibility="visible"/>
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
|
||||
<ProgressBar
|
||||
style="?android:attr/progressBarStyleLarge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/progress"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:visibility="visible"/>
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
</LinearLayout>
|
@ -5,6 +5,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="apps.amine.bou.readerforselfoss.HomeActivity"
|
||||
android:fitsSystemWindows="true"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<com.github.stkent.amplify.prompt.DefaultLayoutPromptView
|
||||
@ -26,6 +27,7 @@
|
||||
app:prompt_view_positive_button_border_color="@color/colorPrimary"
|
||||
app:prompt_view_negative_button_background_color="@color/colorAccent"
|
||||
app:prompt_view_negative_button_border_color="@color/white"
|
||||
app:prompt_view_background_color="?attr/colorAccent"
|
||||
app:prompt_view_thanks_display_time_ms="2000"/>
|
||||
|
||||
<android.support.design.widget.CoordinatorLayout
|
||||
@ -34,34 +36,77 @@
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@id/prompt_view">
|
||||
|
||||
|
||||
<android.support.v4.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swipeRefreshLayout"
|
||||
<android.support.design.widget.CoordinatorLayout
|
||||
android:id="@+id/intern_coordLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/my_recycler_view"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/background_grey"
|
||||
android:clipToPadding="false"
|
||||
android:scrollbars="vertical"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||
</android.support.v4.widget.SwipeRefreshLayout>
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.roughike.bottombar.BottomBar
|
||||
<android.support.design.widget.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:theme="@style/ToolBarStyle" />
|
||||
|
||||
</android.support.design.widget.AppBarLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/drawer_layout"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<android.support.v4.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swipeRefreshLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="?android:attr/windowBackground">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/emptyText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="fill"
|
||||
android:paddingTop="100dp"
|
||||
android:text="@string/nothing_here"
|
||||
android:textAlignment="center"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Headline"
|
||||
android:background="@color/transparent"
|
||||
android:visibility="gone" />
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/my_recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/transparent"
|
||||
android:clipToPadding="false"
|
||||
android:scrollbars="vertical"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||
</LinearLayout>
|
||||
|
||||
</android.support.v4.widget.SwipeRefreshLayout>
|
||||
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</android.support.design.widget.CoordinatorLayout>
|
||||
<com.ashokvarma.bottomnavigation.BottomNavigationBar
|
||||
android:layout_gravity="bottom"
|
||||
android:id="@+id/bottomBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
android:layout_gravity="bottom"
|
||||
app:bb_behavior="shy"
|
||||
android:layout_alignParentBottom="true"
|
||||
app:bb_tabXmlResource="@xml/bottombar"
|
||||
app:bb_activeTabColor="@color/white"
|
||||
app:bb_badgeBackgroundColor="@color/colorPrimary"
|
||||
app:bb_inActiveTabColor="@color/black"
|
||||
app:bb_badgesHideWhenActive="false" />
|
||||
|
||||
android:layout_height="60dp"/>
|
||||
</android.support.design.widget.CoordinatorLayout>
|
||||
</RelativeLayout>
|
@ -2,136 +2,168 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
tools:context="apps.amine.bou.readerforselfoss.LoginActivity">
|
||||
|
||||
<!-- Login progress -->
|
||||
<ProgressBar
|
||||
android:id="@+id/login_progress"
|
||||
style="?android:attr/progressBarStyleLarge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/login_form"
|
||||
<android.support.design.widget.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:theme="@style/ToolBarStyle" />
|
||||
|
||||
</android.support.design.widget.AppBarLayout>
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin">
|
||||
<!-- Login progress -->
|
||||
<ProgressBar
|
||||
android:id="@+id/login_progress"
|
||||
style="?android:attr/progressBarStyleLarge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
android:layout_marginBottom="8dp"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<android.support.design.widget.TextInputLayout
|
||||
<ScrollView
|
||||
android:id="@+id/login_form"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
>
|
||||
android:orientation="vertical">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/url"
|
||||
<android.support.design.widget.TextInputLayout
|
||||
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:id="@+id/urlLayout"
|
||||
>
|
||||
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
<EditText
|
||||
android:id="@+id/url"
|
||||
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"
|
||||
/>
|
||||
|
||||
<Switch
|
||||
android:text="@string/withLoginSwitch"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:id="@+id/withLogin"
|
||||
android:layout_weight="1"/>
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:id="@+id/loginLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone">
|
||||
<Switch
|
||||
android:text="@string/withLoginSwitch"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:id="@+id/withLogin"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<AutoCompleteTextView
|
||||
android:id="@+id/login"
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:id="@+id/loginLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/prompt_login"
|
||||
android:inputType="text"
|
||||
android:maxLines="1"
|
||||
/>
|
||||
android:visibility="gone">
|
||||
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
<AutoCompleteTextView
|
||||
android:id="@+id/login"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/prompt_login"
|
||||
android:inputType="text"
|
||||
android:maxLines="1"
|
||||
/>
|
||||
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:id="@+id/passwordLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone">
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/password"
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:id="@+id/passwordLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/prompt_password"
|
||||
android:inputType="textPassword"
|
||||
android:maxLines="1"
|
||||
/>
|
||||
android:visibility="gone">
|
||||
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
<EditText
|
||||
android:id="@+id/password"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/prompt_password"
|
||||
android:inputType="textPassword"
|
||||
android:maxLines="1"
|
||||
/>
|
||||
|
||||
<Switch
|
||||
android:id="@+id/withHttpLogin"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/withHttpLoginSwitch" />
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:id="@+id/httpLoginInput"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/httpLogin"
|
||||
<Switch
|
||||
android:id="@+id/withHttpLogin"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/prompt_http_login" />
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
android:layout_weight="1"
|
||||
android:text="@string/withHttpLoginSwitch" />
|
||||
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:id="@+id/httpPasswordInput"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone">
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:id="@+id/httpLoginInput"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/httpPassword"
|
||||
<EditText
|
||||
android:id="@+id/httpLogin"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/prompt_http_login" />
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:id="@+id/httpPasswordInput"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/httpPassword"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textPassword"
|
||||
android:hint="@string/prompt_http_password" />
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
<Switch
|
||||
android:id="@+id/withSelfhostedCert"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textPassword"
|
||||
android:hint="@string/prompt_http_password" />
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
android:text="@string/self_hosted_cert_switch" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/email_sign_in_button"
|
||||
style="?android:textAppearanceSmall"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/action_sign_in"
|
||||
android:textStyle="bold" />
|
||||
<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"
|
||||
style="?android:textAppearanceSmall"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/action_sign_in"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</LinearLayout>
|
||||
|
@ -5,7 +5,8 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="apps.amine.bou.readerforselfoss.ReaderActivity">
|
||||
tools:context="apps.amine.bou.readerforselfoss.ReaderActivity"
|
||||
android:background="?android:attr/windowBackground">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
@ -62,5 +63,64 @@
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/title" />
|
||||
app:layout_constraintTop_toBottomOf="@+id/title"
|
||||
tools:text="Some text @android:string/fingerprint_icon_content_description" />
|
||||
|
||||
<android.support.constraint.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="35dp"
|
||||
android:layout_marginBottom="0dp"
|
||||
android:layout_marginEnd="0dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="#BBBBBB"
|
||||
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">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/browserBtn"
|
||||
android:layout_width="35dp"
|
||||
android:layout_height="35dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_toLeftOf="@+id/shareBtn"
|
||||
android:layout_toStartOf="@+id/shareBtn"
|
||||
android:adjustViewBounds="true"
|
||||
android:background="@android:color/transparent"
|
||||
android:elevation="5dp"
|
||||
android:padding="4dp"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/ic_open_in_browser_black_24dp"
|
||||
android:tint="?android:attr/textColorPrimary"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/shareBtn"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageButton
|
||||
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" />
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
|
||||
</android.support.constraint.ConstraintLayout>
|
||||
|
@ -8,12 +8,22 @@
|
||||
android:layout_height="match_parent"
|
||||
tools:context="apps.amine.bou.readerforselfoss.SourcesActivity"
|
||||
xmlns:fab="http://schemas.android.com/apk/res-auto">
|
||||
<android.support.design.widget.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:theme="@style/ToolBarStyle" />
|
||||
|
||||
</android.support.design.widget.AppBarLayout>
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/activity_sources"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/background_grey"
|
||||
android:scrollbars="vertical"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
</android.support.v7.widget.RecyclerView>
|
||||
@ -24,8 +34,9 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end|bottom|right"
|
||||
android:src="@drawable/ic_add_black_24dp"
|
||||
fab:fab_colorNormal="@color/colorAccent"
|
||||
fab:fab_colorPressed="@color/colorAccentDark"
|
||||
android:tint="?android:textColorPrimary"
|
||||
fab:fab_colorNormal="?attr/colorAccent"
|
||||
fab:fab_colorPressed="?attr/colorAccentDark"
|
||||
fab:fab_colorRipple="@color/pink"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
|
@ -6,13 +6,15 @@
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<android.support.v7.widget.CardView
|
||||
android:id="@+id/card"
|
||||
xmlns:card_view="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="0dp"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.62"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:cardElevation="2dp"
|
||||
app:cardUseCompatPadding="true"
|
||||
card_view:cardElevation="2dp"
|
||||
card_view:cardUseCompatPadding="true"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
@ -60,24 +62,21 @@
|
||||
android:layout_width="40dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/title"
|
||||
tools:text="Titre"
|
||||
android:fontFamily="sans-serif"
|
||||
android:textSize="16sp"
|
||||
android:textAllCaps="false"
|
||||
android:textAlignment="viewStart"
|
||||
android:textColor="@color/black"
|
||||
android:layout_marginEnd="8dp"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:gravity="start"
|
||||
android:textAlignment="viewStart"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintLeft_toRightOf="@+id/sourceImage"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/sourceImage"
|
||||
android:gravity="start" />
|
||||
tools:text="Titre" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
@ -85,7 +84,6 @@
|
||||
android:id="@+id/sourceTitleAndDate"
|
||||
android:textSize="14sp"
|
||||
tools:text="Google Actualité Il y a 5h"
|
||||
android:textColor="#868686"
|
||||
android:textAlignment="viewStart"
|
||||
app:layout_constraintTop_toBottomOf="@+id/title"
|
||||
android:layout_marginTop="8dp"
|
||||
@ -117,38 +115,38 @@
|
||||
android:layout_marginEnd="8dp"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/shareBtn"
|
||||
android:layout_width="35dp"
|
||||
android:layout_height="35dp"
|
||||
android:src="@drawable/ic_share_black_24dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_toLeftOf="@+id/favButton"
|
||||
android:layout_toStartOf="@+id/favButton"
|
||||
android:id="@+id/shareBtn"
|
||||
android:adjustViewBounds="true"
|
||||
android:background="@android:color/transparent"
|
||||
android:elevation="5dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:tint="#000000"
|
||||
android:adjustViewBounds="true"
|
||||
android:padding="4dp"
|
||||
android:scaleType="centerCrop"
|
||||
android:padding="4dp"/>
|
||||
android:src="@drawable/ic_share_black_24dp"
|
||||
android:tint="?android:attr/textColorPrimary" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/browserBtn"
|
||||
android:layout_width="35dp"
|
||||
android:layout_height="35dp"
|
||||
android:src="@drawable/ic_open_in_browser_black_24dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_toLeftOf="@+id/shareBtn"
|
||||
android:layout_toStartOf="@+id/shareBtn"
|
||||
android:id="@+id/browserBtn"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:elevation="5dp"
|
||||
android:background="@android:color/transparent"
|
||||
android:tint="#000000"
|
||||
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:padding="4dp"/>
|
||||
android:src="@drawable/ic_open_in_browser_black_24dp"
|
||||
android:tint="?android:attr/textColorPrimary" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
|
@ -3,11 +3,10 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="horizontal"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="88dp"
|
||||
android:background="#EDEDED">
|
||||
android:minHeight="88dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/itemImage"
|
||||
@ -20,25 +19,25 @@
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/title"
|
||||
tools:text="Titre"
|
||||
android:fontFamily="sans-serif"
|
||||
android:textSize="16sp"
|
||||
android:textAllCaps="false"
|
||||
android:textAlignment="viewStart"
|
||||
app:layout_constraintLeft_toRightOf="@+id/itemImage"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:textColor="@color/black"
|
||||
android:layout_marginEnd="16dp"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:fontFamily="sans-serif"
|
||||
android:gravity="start"
|
||||
android:textAlignment="viewStart"
|
||||
android:textAllCaps="false"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
android:gravity="start" />
|
||||
app:layout_constraintLeft_toRightOf="@+id/itemImage"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Titre" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
@ -46,7 +45,6 @@
|
||||
android:id="@+id/sourceTitleAndDate"
|
||||
android:textSize="14sp"
|
||||
tools:text="Google Actualité Il y a 5h"
|
||||
android:textColor="#868686"
|
||||
android:textAlignment="viewStart"
|
||||
app:layout_constraintTop_toBottomOf="@+id/title"
|
||||
android:layout_marginTop="8dp"
|
||||
@ -54,65 +52,64 @@
|
||||
android:gravity="start" />
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/actionBar"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="#BBBBBB"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/sourceTitleAndDate"
|
||||
android:id="@+id/actionBar"
|
||||
android:background="#BBBBBB"
|
||||
android:visibility="gone">
|
||||
|
||||
app:layout_constraintTop_toBottomOf="@+id/sourceTitleAndDate">
|
||||
|
||||
<com.like.LikeButton
|
||||
app:icon_type="heart"
|
||||
app:icon_size="22dp"
|
||||
android:id="@+id/favButton"
|
||||
android:layout_width="35dp"
|
||||
android:layout_height="35dp"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:elevation="5dp"
|
||||
android:padding="4dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginRight="8dp"/>
|
||||
app:icon_size="22dp"
|
||||
app:icon_type="heart" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/shareBtn"
|
||||
android:layout_width="35dp"
|
||||
android:layout_height="35dp"
|
||||
android:src="@drawable/ic_share_black_24dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_toLeftOf="@+id/favButton"
|
||||
android:layout_toStartOf="@+id/favButton"
|
||||
android:id="@+id/shareBtn"
|
||||
android:background="@android:color/transparent"
|
||||
android:elevation="5dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:tint="#000000"
|
||||
android:adjustViewBounds="true"
|
||||
android:background="@android:color/transparent"
|
||||
android:backgroundTint="?android:attr/textColorPrimary"
|
||||
android:elevation="5dp"
|
||||
android:padding="4dp"
|
||||
android:scaleType="centerCrop"
|
||||
android:padding="4dp"/>
|
||||
android:src="@drawable/ic_share_black_24dp" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/browserBtn"
|
||||
android:layout_width="35dp"
|
||||
android:layout_height="35dp"
|
||||
android:src="@drawable/ic_open_in_browser_black_24dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_toLeftOf="@+id/shareBtn"
|
||||
android:layout_toStartOf="@+id/shareBtn"
|
||||
android:id="@+id/browserBtn"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:elevation="5dp"
|
||||
android:background="@android:color/transparent"
|
||||
android:tint="#000000"
|
||||
android:layout_toLeftOf="@+id/shareBtn"
|
||||
android:layout_toStartOf="@+id/shareBtn"
|
||||
android:adjustViewBounds="true"
|
||||
android:background="@android:color/transparent"
|
||||
android:backgroundTint="?android:attr/textColorPrimary"
|
||||
android:elevation="5dp"
|
||||
android:padding="4dp"
|
||||
android:scaleType="centerCrop"
|
||||
android:padding="4dp"/>
|
||||
android:src="@drawable/ic_open_in_browser_black_24dp" />
|
||||
</RelativeLayout>
|
||||
|
||||
|
||||
|
14
app/src/main/res/layout/settings_toolbar.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.design.widget.AppBarLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:theme="@style/ToolBarStyle" />
|
||||
|
||||
</android.support.design.widget.AppBarLayout>
|
@ -5,7 +5,6 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:background="#EDEDED"
|
||||
android:layout_height="48dp">
|
||||
|
||||
|
||||
@ -37,6 +36,7 @@
|
||||
android:id="@+id/deleteBtn"
|
||||
android:background="@drawable/ic_remove_circle_outline_black_24dp"
|
||||
style="@style/Widget.AppCompat.Button.Borderless"
|
||||
android:backgroundTint="?android:textColorSecondary"
|
||||
android:elevation="4dp"
|
||||
android:layout_marginTop="16dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
|
11
app/src/main/res/layout/switch_item.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<Switch
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_centerVertical="true" />
|
||||
</RelativeLayout>
|
@ -2,36 +2,28 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item android:id="@+id/action_search"
|
||||
android:title="@string/menu_home_search"
|
||||
android:icon="@drawable/ic_action_search"
|
||||
app:showAsAction="always|collapseActionView"
|
||||
app:actionViewClass="android.support.v7.widget.SearchView" />
|
||||
|
||||
<item android:id="@+id/readAll"
|
||||
android:icon="@drawable/ic_done_all_white_24dp"
|
||||
android:title="@string/readAll"
|
||||
android:orderInCategory="1"
|
||||
app:showAsAction="ifRoom"/>
|
||||
app:showAsAction="always"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/refresh"
|
||||
android:icon="@drawable/ic_refresh"
|
||||
android:orderInCategory="99"
|
||||
android:title="@string/menu_home_refresh"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item android:id="@+id/action_settings"
|
||||
android:title="@string/title_activity_settings"
|
||||
android:orderInCategory="100"
|
||||
app:showAsAction="never"/>
|
||||
|
||||
<item android:id="@+id/action_sources"
|
||||
android:title="@string/action_source"
|
||||
android:orderInCategory="101"
|
||||
app:showAsAction="never"/>
|
||||
android:title="@string/menu_home_refresh" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_share_the_app"
|
||||
android:orderInCategory="102"
|
||||
android:title="@string/menu_share_the_app" />
|
||||
<item android:id="@+id/about"
|
||||
android:title="@string/action_about"
|
||||
android:orderInCategory="103"
|
||||
app:showAsAction="never"/>
|
||||
|
||||
<item android:id="@+id/action_disconnect"
|
||||
android:title="@string/action_disconnect"
|
||||
|
@ -2,6 +2,14 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/loging_debug"
|
||||
android:checkable="true"
|
||||
android:checked="false"
|
||||
android:icon="@drawable/ic_bug_report"
|
||||
android:title="@string/login_menu_debug"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item android:id="@+id/about"
|
||||
android:title="@string/action_about"
|
||||
android:orderInCategory="102"
|
||||
|
5
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|