Compare commits

..

102 Commits

Author SHA1 Message Date
54b2ac7f24 Fixes #10. Fixes #68. 2017-09-10 12:59:51 +02:00
12356a35fa Solved compilation warning. 2017-09-10 09:48:20 +02:00
12262304ac Trying to fix minification problem. 2017-09-10 09:19:28 +02:00
c58f97452e Fixed issues with secrets xml file. Removed it and remplaced with build config. 2017-09-06 21:17:01 +02:00
eb3872f7a6 Added a setting for displaying or hiding the account header. 2017-09-03 11:47:48 +02:00
9fa178d513 Closes #71 2017-09-02 17:55:02 +02:00
043b184065 Images are now loading on self signed certs. 2017-09-02 13:58:35 +02:00
10559bb894 Fixed build problem from last commit. 2017-09-02 12:51:11 +02:00
d0000d66b2 Fixes #70. Updated glide for images loading. 2017-09-02 10:02:35 +02:00
b447ac738a Fixes #62. 2017-08-31 20:00:58 +02:00
faebfc238c Closes #69. 2017-08-30 22:11:29 +02:00
c28fbd37cc Fixed issue with last commit. 2017-08-30 07:34:48 +02:00
4b8396959d Needed to add an exception for the log to work. 2017-08-30 07:22:28 +02:00
b39d510e07 Added reading article log. 2017-08-29 22:49:20 +02:00
286dda7f80 Update README.md 2017-08-27 19:33:21 +02:00
7bda896e2d Added the ability to choose the number of items loaded. 2017-08-26 21:36:19 +02:00
ba4feeea87 Trying to fix pre-lolipop svg drawable loading. 2017-08-16 22:15:33 +02:00
6f52eae3c6 Version updates. 2017-08-16 21:18:15 +02:00
40ea8d56e6 Closes #64 2017-08-16 20:18:49 +02:00
72e562e8a8 Reverted back to the old icon. 2017-08-16 07:51:28 +02:00
6fa01bfe19 Added changelog. 2017-08-04 21:44:29 +02:00
0ef59c9b91 Added a quick fix for accepting self signed certificates. 2017-08-04 21:42:35 +02:00
d768d2232b Added login optional debug logs. (#61) 2017-08-04 08:30:21 +02:00
b44a200731 A better icon. 2017-08-03 07:43:00 +02:00
016815e0d1 This test keep failing of firebase test lab. 2017-08-02 21:01:51 +02:00
590534e4a6 Splash screen fix, with some test problem. 2017-08-02 20:53:20 +02:00
7ea9d4e519 Changelog 2017-08-02 20:11:02 +02:00
e0ab09f533 Adaptive icon (#60)
* Removed mipmap folder from gitignore.

* New adaptive icon.

* Removed icon step from build.

* The icon seamed pixelated.
2017-08-02 20:08:31 +02:00
fbe98f1b16 Update CONTRIBUTING.md 2017-08-02 07:32:28 +02:00
d0675b8443 Update build.gradle 2017-07-30 19:01:05 +02:00
3ea1ed02ae Update build.gradle 2017-07-30 18:58:36 +02:00
ba120b1e0b Ignoring annoying bug with Samsung devices on 4.2.2 (#57)
* Updated gradle version.

* Fixed #55. Ignoring annoying non fatal bug on Samsung devices.
2017-07-30 18:37:44 +02:00
acf6995c2d Trying to handle app versionning from git tag. 2017-07-28 05:02:28 -04:00
8306860f90 Fixed #54 2017-07-28 07:33:21 +02:00
65974166be Update README.md 2017-07-27 22:03:57 +02:00
ee8924f986 Removed travis integration. 2017-07-27 22:02:14 +02:00
170e575465 Hiding the badge on reading all. 2017-07-27 22:02:14 +02:00
b7d5317b10 Fixed travis build. 2017-07-25 21:35:57 +02:00
f12e7748c5 Build v1.5.1.8 2017-07-25 21:17:34 +02:00
69a2418afc Fix 51 (#52)
* Some more bug fixes.

* Cleaning travis file.

* Fixes #51
2017-07-22 13:12:13 +02:00
4924ddd172 Changed CI from circle to travis. 2017-07-21 18:48:13 +02:00
1889b43786 Build and changelog. 2017-07-21 11:44:13 +02:00
f2e38a4203 Bug fixes (#49)
* Version updates.

* Fixed 'Calling startActivity() from outside of an Activity'

* Fixes #47

* Anydpi icons causing crashes.
2017-07-14 09:29:25 +02:00
90a8fac8d4 Added back the badges. 2017-07-10 22:18:42 +02:00
04402c5ab9 Build. 2017-07-09 19:26:14 +02:00
f8f710df99 Working themes. Needs some cleaning. Closes #37 . 2017-07-09 19:07:52 +02:00
b8105bb6fb Update README.md 2017-07-05 15:00:31 +02:00
1d18c898b2 Build. 2017-07-03 19:29:35 +02:00
95e208000f Fixes #39 2017-07-03 19:27:30 +02:00
ecdddef81d Changelog. 2017-07-02 19:50:45 +02:00
c9b1d329e6 Fixes #30 2017-07-02 19:36:37 +02:00
e68c16c7a4 Fixes #28 2017-07-02 19:31:18 +02:00
585c57fe3a Fixed circleci after apdate. 2017-07-02 08:38:43 +02:00
d04cbac79c Versions update and fixes. 2017-07-02 08:19:07 +02:00
044585ee9b Missing translation. 2017-07-02 08:09:17 +02:00
299478e840 This should fix #35 2017-07-02 07:18:56 +02:00
b2d69be5f8 This should fix #34 2017-07-02 07:14:29 +02:00
dc970bbf3c Fixed readme link. 2017-06-29 19:44:52 +02:00
8717bd5d5d Added templates. 2017-06-29 19:43:30 +02:00
5b307a8407 Update CONTRIBUTING.md 2017-06-29 17:06:38 +02:00
daef66087d Update README.md 2017-06-29 11:04:18 +02:00
1ad1cf4460 Update CONTRIBUTING.md 2017-06-29 11:04:04 +02:00
c0b9718368 Create CONTRIBUTING.md 2017-06-29 11:01:15 +02:00
d684f323b8 Cleaning. 2017-06-28 21:02:15 +02:00
24a1c56fe6 Update README.md 2017-06-28 13:25:53 +02:00
cdeba4f84e gradle update. 2017-06-18 08:29:54 +02:00
cafba196cf Some more code cleaning. 2017-06-14 21:26:26 +02:00
493b1b12b3 Using extension methods to clean the code a little. 2017-06-14 21:10:53 +02:00
5320f88230 Some code cleaning. 2017-06-13 22:03:41 +02:00
246ec2c3ac Should fix #27 2017-06-13 21:38:35 +02:00
9c9b45aeab Kotlin version update. 2017-06-13 21:38:35 +02:00
8c5dc43735 Create README.md 2017-06-13 14:54:04 +02:00
b1e812314f Added tests and testing in ci. 2017-06-12 19:44:11 +02:00
c14f47a74b Code cleaning. 2017-06-10 07:40:02 +02:00
58a5b4a5e5 Create README.md 2017-06-08 21:34:36 +02:00
1cfc2bf36f Create README.md 2017-06-08 21:28:15 +02:00
5a56d826d9 Changelog for 1.5.1.1 2017-06-08 21:11:23 +02:00
8ad8b55424 Drawer reloading on slide to refresh. 2017-06-08 20:43:22 +02:00
3da1d431db Fixes #23. 2017-06-08 20:35:50 +02:00
4565079f29 More filtering with the search toolbar. 2017-06-07 08:59:13 +02:00
3482092cb2 Filters not reset on resume. 2017-06-07 07:35:55 +02:00
2df5e52de0 Fixes #6 2017-06-07 07:28:17 +02:00
0ef4fc67fa Fixing cache issue. May need some real solution. 2017-06-06 21:13:46 +02:00
e2bfd549d3 Build and changelog. 2017-06-06 20:04:00 +02:00
7071af5fa5 Added back unread count. 2017-06-06 20:02:05 +02:00
95f267f701 Switched primary <=> secodary items for drawer.
Added source icon fetching.
2017-06-06 20:01:33 +02:00
f363bbcc0c Fixed an issue with the article reader. 2017-06-06 18:54:17 +02:00
65a912f271 Finally fixed #16 and fixed #17. 2017-06-05 21:17:51 +02:00
758661f5fb Fixed #16, fixes #17 and fixes #19 2017-06-05 18:50:19 +02:00
2d3c297726 Fixes #14 and fixes #15 2017-06-04 20:26:05 +02:00
ca85e3d3ed Changelog for previous version. 2017-06-04 18:24:31 +02:00
2190ad0387 Forced nl strings to english. 2017-06-04 18:13:29 +02:00
0d067e05af Fixed drawable on landscape and extracted strings to translation. 2017-06-04 18:09:02 +02:00
c3305b7523 Moved some menu items to the drawable. 2017-06-04 16:44:31 +02:00
fb84b31122 Drawer with caching. 2017-06-04 16:09:42 +02:00
9d40026ef7 Build and changeelog. 2017-06-04 09:47:45 +02:00
041a225992 Updated Custom tab code and added buttons at the bottom of the article viewer. 2017-06-03 22:52:01 +02:00
b42dc7f87c Update README.md 2017-05-30 20:37:27 +02:00
0283e49c27 Testing circle ci. 2017-05-30 20:35:01 +02:00
e5e1b2f5a5 Update README.md 2017-05-29 19:42:38 +02:00
813a0ae475 Update README.md 2017-05-29 19:13:42 +02:00
f0e036cdd8 Update README.md 2017-05-28 18:31:39 +02:00
128 changed files with 4154 additions and 1490 deletions

75
.github/CONTRIBUTING.md vendored Normal file
View 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
View 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
View 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

3
.gitignore vendored
View File

@ -214,7 +214,4 @@ gradle-app.setting
# End of https://www.gitignore.io/api/java,gradle,android,androidstudio
secrets.xml
mipmap-*
release/

View File

@ -1,3 +1,136 @@
**1.5.2.18/19**
- APK minification finally working. That means less space taken !
**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.

View File

@ -1,29 +1,24 @@
# ReaderForSelfoss
[![Build Status](http://jenkins.amine-bou.fr/job/ReaderForSelfoss/badge/icon)](http://jenkins.amine-bou.fr/job/ReaderForSelfoss/)
[![Gitter chat](https://badges.gitter.im/amine-bou/ReaderForSelfoss.png)](https://gitter.im/amine-bou/ReaderForSelfoss)
[![codebeat badge](https://codebeat.co/badges/bce66c0f-fd28-4341-a159-3b6dd22ac854)](https://codebeat.co/projects/github-com-aminecmi-readerforselfoss-master)
[![Code Triagers Badge](https://www.codetriage.com/aminecmi/readerforselfoss/badges/users.svg)](https://www.codetriage.com/aminecmi/readerforselfoss)
This is the repo of [Reader For Selfoss](https://play.google.com/store/apps/details?id=apps.amine.bou.readerforselfoss&hl=en).
It's an RSS Reader for Android, that **only** works with [Selfoss](https://selfoss.aditu.de/)
## Build
## Want to help ?
You can directly import this project into IntellIJ/Android Studio.
You'll have to:
- [Create your own launcher icon](https://developer.android.com/studio/write/image-asset-studio.html#creating-launcher)
- Configure Fabric, 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)

View File

@ -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,27 @@ 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
}
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,35 +88,44 @@ 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
}
@ -98,27 +136,76 @@ dependencies {
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.0@aar') {
transitive = true;
}
compile('com.github.bumptech.glide:okhttp3-integration:4.1.0@aar')
// 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"))
}
}
}

View File

@ -56,4 +56,12 @@
#Bottom bar lib
-dontwarn com.roughike.bottombar.**
-dontwarn com.roughike.bottombar.**
# self signed glidemodule
-keep public class * implements com.bumptech.glide.module.GlideModule
-dontwarn com.anupcowkur.reservoir.**
-dontwarn javax.annotation.**

View File

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

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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)
)

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -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()

View File

@ -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() {

View File

@ -9,20 +9,14 @@ 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 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 android.widget.*
import com.google.firebase.analytics.FirebaseAnalytics
import com.mikepenz.aboutlibraries.Libs
import com.mikepenz.aboutlibraries.LibsBuilder
@ -30,57 +24,97 @@ import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
import apps.amine.bou.readerforselfoss.utils.Config
import apps.amine.bou.readerforselfoss.utils.checkAndDisplayStoreApk
import apps.amine.bou.readerforselfoss.utils.isBaseUrlValid
import com.crashlytics.android.Crashlytics
import com.ftinc.scoop.Scoop
import io.fabric.sdk.android.Fabric
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 +126,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 +150,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 +175,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 +186,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 +256,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 +291,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)

View File

@ -6,6 +6,7 @@ import android.preference.PreferenceManager
import android.support.v7.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {

View File

@ -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)
}
}
}

View File

@ -6,12 +6,10 @@ 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 com.bumptech.glide.Glide
import org.sufficientlysecure.htmltextview.HtmlHttpImageGetter
import org.sufficientlysecure.htmltextview.HtmlTextView
@ -20,62 +18,96 @@ import retrofit2.Callback
import retrofit2.Response
import xyz.klinker.android.drag_dismiss.activity.DragDismissActivity
import apps.amine.bou.readerforselfoss.api.mercury.MercuryApi
import apps.amine.bou.readerforselfoss.api.mercury.ParsedContent
import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
import apps.amine.bou.readerforselfoss.utils.openItemUrl
import apps.amine.bou.readerforselfoss.utils.shareLink
import com.bumptech.glide.request.RequestOptions
import com.ftinc.scoop.Scoop
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()
}
})

View File

@ -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)

View File

@ -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)
}
}
}

View File

@ -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
}
}
}

View File

@ -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()
}
})
}

View File

@ -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)
}

View File

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

View File

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

View File

@ -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> {

View File

@ -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?): Call<List<Item>> =
getItems("read", tag, sourceId, search, 200)
val unreadItems: Call<List<Item>>
get() = getItems("unread")
fun newItems(tag: String?, sourceId: Long?, search: String?, itemsNumber: Int): Call<List<Item>> =
getItems("unread", tag, sourceId, search, itemsNumber)
val starredItems: Call<List<Item>>
get() = getItems("starred")
fun starredItems(tag: String?, sourceId: Long?, search: String?): Call<List<Item>> =
getItems("starred", tag, sourceId, search, 200)
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): Call<List<Item>> =
service.getItems(type, tag, sourceId, search, userName, password, items)
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]

View File

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

View File

@ -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,87 @@ 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): 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]

View File

@ -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);
}

View File

@ -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,7 @@ 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;
}
});
@ -199,6 +268,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();

View File

@ -0,0 +1,53 @@
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
import java.lang.reflect.AccessibleObject.setAccessible
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
}
}

View File

@ -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..l - 1) {
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)
}

View File

@ -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
}
}
}

View File

@ -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)
}

View File

@ -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 {
var 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
}

View File

@ -7,75 +7,84 @@ import android.content.Intent
import android.graphics.BitmapFactory
import android.net.Uri
import android.support.customtabs.CustomTabsIntent
import xyz.klinker.android.drag_dismiss.DragDismissIntentBuilder
import 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)
}

View File

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

View File

@ -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);
}
}
}

View File

@ -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();
}
}

View File

@ -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();
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
})

View File

@ -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))
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 680 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 458 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 551 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 953 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 498 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 655 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 634 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 504 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 725 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 606 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 466 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 741 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 907 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 573 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 966 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -2,12 +2,12 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:drawable="@color/white"/>
android:drawable="@color/ic_launcher_background"/>
<item>
<bitmap
android:gravity="center"
android:src="@mipmap/ic_launcher"/>
android:src="@mipmap/ic_launcher_foreground"/>
</item>
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

View File

@ -0,0 +1,4 @@
<vector android:height="24dp" android:viewportHeight="24.0"
android:viewportWidth="24.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FFFFFF" android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/>
</vector>

View File

@ -0,0 +1,4 @@
<vector android:height="24dp" android:viewportHeight="24.0"
android:viewportWidth="24.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FFFFFF" android:pathData="M1,21h4L5,9L1,9v12zM23,10c0,-1.1 -0.9,-2 -2,-2h-6.31l0.95,-4.57 0.03,-0.32c0,-0.41 -0.17,-0.79 -0.44,-1.06L14.17,1 7.59,7.59C7.22,7.95 7,8.45 7,9v10c0,1.1 0.9,2 2,2h9c0.83,0 1.54,-0.5 1.84,-1.22l3.02,-7.05c0.09,-0.23 0.14,-0.47 0.14,-0.73v-1.91l-0.01,-0.01L23,10z"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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"

View File

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

View File

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

View 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>

View File

@ -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"

View 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>

View File

@ -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"

View File

@ -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"

View 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>

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