Compare commits

..

61 Commits

Author SHA1 Message Date
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
85 changed files with 3236 additions and 1397 deletions

55
.github/CONTRIBUTING.md vendored Normal file
View File

@ -0,0 +1,55 @@
# 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 every usefull 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:
- [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

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

View File

@ -1,3 +1,60 @@
**1.5.1.9**
- 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** **1.5.0.2**
- If the content in the article viewer is empty, the article will open in a custom tab. - If the content in the article viewer is empty, the article will open in a custom tab.

View File

@ -1,27 +1,21 @@
# ReaderForSelfoss # ReaderForSelfoss
[![CircleCI](https://circleci.com/gh/aminecmi/ReaderforSelfoss/tree/master.svg?style=svg)](https://circleci.com/gh/aminecmi/ReaderforSelfoss/tree/master) [![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). This is the repo of [Reader For Selfoss](https://play.google.com/store/apps/details?id=apps.amine.bou.readerforselfoss&hl=en).
It's an RSS Reader for Android, that **only** works with [Selfoss](https://selfoss.aditu.de/) It's an RSS Reader for Android, that **only** works with [Selfoss](https://selfoss.aditu.de/)
## Build ## Want to help ?
You can directly import this project into IntellIJ/Android Studio. Check the [Contribution guide](https://github.com/aminecmi/ReaderforSelfoss/blob/master/.github/CONTRIBUTING.md)
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
## Useful links ## Useful links

View File

@ -19,14 +19,14 @@ repositories {
} }
android { android {
compileSdkVersion 25 compileSdkVersion 26
buildToolsVersion "25.0.3" buildToolsVersion "26.0.0"
defaultConfig { defaultConfig {
applicationId "apps.amine.bou.readerforselfoss" applicationId "apps.amine.bou.readerforselfoss"
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 25 targetSdkVersion 26
versionCode 1502 versionCode 1519
versionName "1.5.0.2" versionName "1.5.1.9"
// Enabling multidex support. // Enabling multidex support.
multiDexEnabled true multiDexEnabled true
@ -35,6 +35,9 @@ android {
disable 'InvalidPackage' disable 'InvalidPackage'
} }
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
// tests
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
} }
buildTypes { buildTypes {
release { release {
@ -42,6 +45,11 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro' 'proguard-rules.pro'
} }
debug {
buildConfigField "String", "LOGIN_URL", appLoginUrl
buildConfigField "String", "LOGIN_USERNAME", appLoginUsername
buildConfigField "String", "LOGIN_PASSWORD", appLoginPassword
}
} }
flavorDimensions "build" flavorDimensions "build"
productFlavors { productFlavors {
@ -59,23 +67,32 @@ android {
} }
dependencies { dependencies {
// Testing
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
androidTestCompile 'com.android.support.test:runner:0.5'
// Espresso-contrib for DatePicker, RecyclerView, Drawer actions, Accessibility checks, CountingIdlingResource
androidTestCompile 'com.android.support.test.espresso:espresso-contrib:2.2.2'
// Espresso-intents for validation and stubbing of Intents
androidTestCompile 'com.android.support.test.espresso:espresso-intents:2.2.2'
compile fileTree(dir: 'libs', include: ['*.jar']) compile fileTree(dir: 'libs', include: ['*.jar'])
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
// Android Support // Android Support
compile 'com.android.support:appcompat-v7:25.3.1' compile 'com.android.support:appcompat-v7:26.0.0'
compile 'com.android.support:design:25.3.1' compile 'com.android.support:design:26.0.0'
compile 'com.android.support:recyclerview-v7:25.3.1' compile 'com.android.support:recyclerview-v7:26.0.0'
compile 'com.android.support:support-v4:25.3.1' compile 'com.android.support:support-v4:26.0.0'
compile 'com.android.support:support-vector-drawable:25.3.1' compile 'com.android.support:support-vector-drawable:26.0.0'
compile 'com.android.support:customtabs:25.3.1' compile 'com.android.support:customtabs:26.0.0'
compile 'com.android.support:cardview-v7:25.3.1' compile 'com.android.support:cardview-v7:26.0.0'
compile 'com.android.support.constraint:constraint-layout:1.0.2' compile 'com.android.support.constraint:constraint-layout:1.0.2'
// Firebase + crashlytics // Firebase + crashlytics
compile 'com.google.firebase:firebase-core:10.2.6' compile 'com.google.firebase:firebase-core:11.0.2'
compile 'com.google.firebase:firebase-config:10.2.6' compile 'com.google.firebase:firebase-config:11.0.2'
compile 'com.google.firebase:firebase-invites:10.2.6' compile 'com.google.firebase:firebase-invites:11.0.2'
compile('com.crashlytics.sdk.android:crashlytics:2.6.8@aar') { compile('com.crashlytics.sdk.android:crashlytics:2.6.8@aar') {
transitive = true transitive = true
} }
@ -98,7 +115,7 @@ dependencies {
compile 'com.burgstaller:okhttp-digest:1.12' compile 'com.burgstaller:okhttp-digest:1.12'
// Material-ish things // 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.melnykov:floatingactionbutton:1.3.0'
compile 'com.github.jd-alexander:LikeButton:0.2.1' compile 'com.github.jd-alexander:LikeButton:0.2.1'
compile 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1' compile 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
@ -111,7 +128,16 @@ dependencies {
compile 'com.github.stkent:amplify:1.5.0' compile 'com.github.stkent:amplify:1.5.0'
// For the article reader // For the article reader
compile 'com.klinkerapps:drag-dismiss-activity:1.4.0' compile 'com.klinkerapps:drag-dismiss-activity:1.4.2'
// Drawer
compile('com.mikepenz:materialdrawer:5.9.4@aar') {
transitive = true
}
compile 'com.anupcowkur:reservoir:3.1.0'
// Themes
compile 'com.52inc:scoops:1.0.0'
} }
@ -120,6 +146,7 @@ apply plugin: 'com.google.gms.google-services'
afterEvaluate { afterEvaluate {
initFabricPropertiesIfNeeded() initFabricPropertiesIfNeeded()
initAppLoginPropertiesIfNeeded()
} }
def initFabricPropertiesIfNeeded() { def initFabricPropertiesIfNeeded() {
@ -131,4 +158,16 @@ def initFabricPropertiesIfNeeded() {
entry(key: "apiKey", value: crashlyticsdemoApikey) 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"))
}
}
} }

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:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme"> android:theme="@style/NoBar">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:theme="@style/SplashTheme"> android:theme="@style/SplashTheme">

View File

@ -4,16 +4,20 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.support.constraint.ConstraintLayout import android.support.constraint.ConstraintLayout
import android.support.v7.app.AppCompatActivity import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
import android.view.View import android.view.View
import android.widget.* 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.SelfossApi
import apps.amine.bou.readerforselfoss.api.selfoss.Spout import apps.amine.bou.readerforselfoss.api.selfoss.Spout
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
import apps.amine.bou.readerforselfoss.utils.Config import apps.amine.bou.readerforselfoss.utils.Config
import apps.amine.bou.readerforselfoss.utils.isUrlValid import apps.amine.bou.readerforselfoss.utils.isBaseUrlValid
import retrofit2.Call import com.ftinc.scoop.Scoop
import retrofit2.Callback
import retrofit2.Response
class AddSourceActivity : AppCompatActivity() { class AddSourceActivity : AppCompatActivity() {
@ -22,17 +26,27 @@ class AddSourceActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
Scoop.getInstance().apply(this)
setContentView(R.layout.activity_add_source) 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 mProgress: ProgressBar = findViewById(R.id.progress)
val mForm = findViewById(R.id.formContainer) as ConstraintLayout val mForm: ConstraintLayout = findViewById(R.id.formContainer)
val mNameInput = findViewById(R.id.nameInput) as EditText val mNameInput: EditText = findViewById(R.id.nameInput)
val mSourceUri = findViewById(R.id.sourceUri) as EditText val mSourceUri: EditText = findViewById(R.id.sourceUri)
val mTags = findViewById(R.id.tags) as EditText val mTags: EditText = findViewById(R.id.tags)
val mSpoutsSpinner = findViewById(R.id.spoutsSpinner) as Spinner val mSpoutsSpinner: Spinner = findViewById(R.id.spoutsSpinner)
val mSaveBtn = findViewById(R.id.saveBtn) as Button val mSaveBtn: Button = findViewById(R.id.saveBtn)
val api = SelfossApi(this) var api: SelfossApi? = null
try {
api = SelfossApi(this, this@AddSourceActivity)
} catch (e: IllegalArgumentException) {
mustLoginToAddSource()
}
val intent = intent val intent = intent
if (Intent.ACTION_SEND == intent.action && "text/plain" == intent.type) { if (Intent.ACTION_SEND == intent.action && "text/plain" == intent.type) {
@ -40,7 +54,9 @@ class AddSourceActivity : AppCompatActivity() {
mNameInput.setText(intent.getStringExtra(Intent.EXTRA_TITLE)) 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>() val spoutsKV = HashMap<String, String>()
@ -57,15 +73,12 @@ class AddSourceActivity : AppCompatActivity() {
val config = Config(this) val config = Config(this)
if (config.baseUrl.isEmpty() || !isUrlValid(config.baseUrl)) { if (config.baseUrl.isEmpty() || !config.baseUrl.isBaseUrlValid()) {
Toast.makeText(this, getString(R.string.addStringNoUrl), Toast.LENGTH_SHORT).show() mustLoginToAddSource()
val i = Intent(this, LoginActivity::class.java)
startActivity(i)
finish()
} else { } else {
var items: Map<String, Spout> var items: Map<String, Spout>
api.spouts().enqueue(object : Callback<Map<String, Spout>> { api!!.spouts().enqueue(object : Callback<Map<String, Spout>> {
override fun onResponse(call: Call<Map<String, Spout>>, response: Response<Map<String, Spout>>) { override fun onResponse(call: Call<Map<String, Spout>>, response: Response<Map<String, Spout>>) {
if (response.body() != null) { if (response.body() != null) {
items = response.body()!! items = response.body()!!
@ -78,7 +91,11 @@ class AddSourceActivity : AppCompatActivity() {
mProgress.visibility = View.GONE mProgress.visibility = View.GONE
mForm.visibility = View.VISIBLE 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) spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
mSpoutsSpinner.adapter = spinnerArrayAdapter mSpoutsSpinner.adapter = spinnerArrayAdapter
@ -99,12 +116,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) { private fun handleSaveSource(mTags: EditText, title: String, url: String, api: SelfossApi) {
if (title.isEmpty() || url.isEmpty() || mSpoutsValue == null || mSpoutsValue!!.isEmpty()) { if (title.isEmpty() || url.isEmpty() || mSpoutsValue == null || mSpoutsValue!!.isEmpty()) {
Toast.makeText(this, R.string.form_not_complete, Toast.LENGTH_SHORT).show() Toast.makeText(this, R.string.form_not_complete, Toast.LENGTH_SHORT).show()
} else { } else {
api.createSource(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>) { override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {
if (response.body() != null && response.body()!!.isSuccess) { if (response.body() != null && response.body()!!.isSuccess) {
finish() finish()

View File

@ -1,14 +1,16 @@
package apps.amine.bou.readerforselfoss 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.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.preference.PreferenceManager import android.preference.PreferenceManager
import android.view.View import android.view.View
import agency.tango.materialintroscreen.MaterialIntroActivity
import agency.tango.materialintroscreen.MessageButtonBehaviour
import agency.tango.materialintroscreen.SlideFragmentBuilder
class IntroActivity : MaterialIntroActivity() { class IntroActivity : MaterialIntroActivity() {
@ -16,32 +18,32 @@ class IntroActivity : MaterialIntroActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
addSlide(SlideFragmentBuilder() addSlide(SlideFragmentBuilder()
.backgroundColor(R.color.colorPrimary) .backgroundColor(R.color.colorPrimary)
.buttonsColor(R.color.colorAccent) .buttonsColor(R.color.colorAccent)
.image(R.mipmap.ic_launcher) .image(R.mipmap.ic_launcher)
.title(getString(R.string.intro_hello_title)) .title(getString(R.string.intro_hello_title))
.description(getString(R.string.intro_hello_message)) .description(getString(R.string.intro_hello_message))
.build()) .build())
addSlide(SlideFragmentBuilder() addSlide(SlideFragmentBuilder()
.backgroundColor(R.color.colorAccent) .backgroundColor(R.color.colorAccent)
.buttonsColor(R.color.colorPrimary) .buttonsColor(R.color.colorPrimary)
.image(R.drawable.ic_info_outline_white_48dp) .image(R.drawable.ic_info_outline_white_48dp)
.title(getString(R.string.intro_needs_selfoss_title)) .title(getString(R.string.intro_needs_selfoss_title))
.description(getString(R.string.intro_needs_selfoss_message)) .description(getString(R.string.intro_needs_selfoss_message))
.build(), .build(),
MessageButtonBehaviour(View.OnClickListener { MessageButtonBehaviour(View.OnClickListener {
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://selfoss.aditu.de")) val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://selfoss.aditu.de"))
startActivity(browserIntent) startActivity(browserIntent)
}, getString(R.string.intro_needs_selfoss_link))) }, getString(R.string.intro_needs_selfoss_link)))
addSlide(SlideFragmentBuilder() addSlide(SlideFragmentBuilder()
.backgroundColor(R.color.colorPrimaryDark) .backgroundColor(R.color.colorPrimaryDark)
.buttonsColor(R.color.colorAccentDark) .buttonsColor(R.color.colorAccentDark)
.image(R.drawable.ic_thumb_up_white_48dp) .image(R.drawable.ic_thumb_up_white_48dp)
.title(getString(R.string.intro_all_set_title)) .title(getString(R.string.intro_all_set_title))
.description(getString(R.string.intro_all_set_message)) .description(getString(R.string.intro_all_set_message))
.build()) .build())
} }
override fun onFinish() { override fun onFinish() {

View File

@ -9,6 +9,7 @@ import android.os.Bundle
import android.support.design.widget.TextInputLayout import android.support.design.widget.TextInputLayout
import android.support.v7.app.AlertDialog import android.support.v7.app.AlertDialog
import android.support.v7.app.AppCompatActivity import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
import android.text.TextUtils import android.text.TextUtils
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
@ -18,11 +19,7 @@ import android.widget.Button
import android.widget.EditText import android.widget.EditText
import android.widget.Switch import android.widget.Switch
import android.widget.TextView 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 com.google.firebase.analytics.FirebaseAnalytics import com.google.firebase.analytics.FirebaseAnalytics
import com.mikepenz.aboutlibraries.Libs import com.mikepenz.aboutlibraries.Libs
import com.mikepenz.aboutlibraries.LibsBuilder import com.mikepenz.aboutlibraries.LibsBuilder
@ -30,57 +27,77 @@ import retrofit2.Call
import retrofit2.Callback import retrofit2.Callback
import retrofit2.Response 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.ftinc.scoop.Scoop
class LoginActivity : AppCompatActivity() { 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 inValidCount: Int = 0
private var isWithLogin = false private var isWithLogin = false
private var isWithHTTPLogin = false private var isWithHTTPLogin = false
private var mLoginFormView: View? = null
private var mFirebaseAnalytics: FirebaseAnalytics? = null private lateinit var settings: SharedPreferences
private lateinit var mFirebaseAnalytics: FirebaseAnalytics
private lateinit var mUrlView: EditText
private lateinit var mLoginView: TextView
private lateinit var mHTTPLoginView: TextView
private lateinit var mProgressView: View
private lateinit var mPasswordView: EditText
private lateinit var mHTTPPasswordView: EditText
private lateinit var mLoginFormView: View
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
Scoop.getInstance().apply(this)
setContentView(R.layout.activity_login) setContentView(R.layout.activity_login)
settings = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE) val toolbar: Toolbar = findViewById(R.id.toolbar)
if (settings!!.getString("url", "").isNotEmpty()) { setSupportActionBar(toolbar)
goToMain()
} else { if (intent.getBooleanExtra("baseUrlFail", false)) {
checkAndDisplayStoreApk(this@LoginActivity) 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 settings = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
inValidCount = 0 if (settings.getString("url", "").isNotEmpty()) {
goToMain()
} else {
this@LoginActivity.checkAndDisplayStoreApk()
}
mFirebaseAnalytics = FirebaseAnalytics.getInstance(this) mFirebaseAnalytics = FirebaseAnalytics.getInstance(this)
mUrlView = findViewById(R.id.url) as EditText mUrlView = findViewById(R.id.url)
mLoginView = findViewById(R.id.login) as TextView mLoginView = findViewById(R.id.login)
mHTTPLoginView = findViewById(R.id.httpLogin) as TextView mHTTPLoginView = findViewById(R.id.httpLogin)
mPasswordView = findViewById(R.id.password) as EditText mPasswordView = findViewById(R.id.password)
mHTTPPasswordView = findViewById(R.id.httpPassword) as EditText mHTTPPasswordView = findViewById(R.id.httpPassword)
mLoginFormView = findViewById(R.id.login_form) mLoginFormView = findViewById(R.id.login_form)
mProgressView = findViewById(R.id.login_progress) mProgressView = findViewById(R.id.login_progress)
val mSwitch = findViewById(R.id.withLogin) as Switch val mSwitch: Switch = findViewById(R.id.withLogin)
val mHTTPSwitch = findViewById(R.id.withHttpLogin) as Switch val mHTTPSwitch: Switch = findViewById(R.id.withHttpLogin)
val mLoginLayout = findViewById(R.id.loginLayout) as TextInputLayout val mLoginLayout: TextInputLayout = findViewById(R.id.loginLayout)
val mHTTPLoginLayout = findViewById(R.id.httpLoginInput) as TextInputLayout val mHTTPLoginLayout: TextInputLayout = findViewById(R.id.httpLoginInput)
val mPasswordLayout = findViewById(R.id.passwordLayout) as TextInputLayout val mPasswordLayout: TextInputLayout = findViewById(R.id.passwordLayout)
val mHTTPPasswordLayout = findViewById(R.id.httpPasswordInput) as TextInputLayout val mHTTPPasswordLayout: TextInputLayout = findViewById(R.id.httpPasswordInput)
val mEmailSignInButton = findViewById(R.id.email_sign_in_button) as Button val mEmailSignInButton: Button = findViewById(R.id.email_sign_in_button)
mPasswordView!!.setOnEditorActionListener(TextView.OnEditorActionListener { _, id, _ -> mPasswordView.setOnEditorActionListener(TextView.OnEditorActionListener { _, id, _ ->
if (id == R.id.login || id == EditorInfo.IME_NULL) { if (id == R.id.login || id == EditorInfo.IME_NULL) {
attemptLogin() attemptLogin()
return@OnEditorActionListener true return@OnEditorActionListener true
@ -92,26 +109,16 @@ class LoginActivity : AppCompatActivity() {
mSwitch.setOnCheckedChangeListener { _, b -> mSwitch.setOnCheckedChangeListener { _, b ->
isWithLogin = !isWithLogin isWithLogin = !isWithLogin
val visi: Int val visi: Int = if (b) View.VISIBLE else View.GONE
if (b) {
visi = View.VISIBLE
} else {
visi = View.GONE
}
mLoginLayout.visibility = visi mLoginLayout.visibility = visi
mPasswordLayout.visibility = visi mPasswordLayout.visibility = visi
} }
mHTTPSwitch.setOnCheckedChangeListener { _, b -> mHTTPSwitch.setOnCheckedChangeListener { _, b ->
isWithHTTPLogin = !isWithHTTPLogin isWithHTTPLogin = !isWithHTTPLogin
val visi: Int val visi: Int = if (b) View.VISIBLE else View.GONE
if (b) {
visi = View.VISIBLE
} else {
visi = View.GONE
}
mHTTPLoginLayout.visibility = visi mHTTPLoginLayout.visibility = visi
mHTTPPasswordLayout.visibility = visi mHTTPPasswordLayout.visibility = visi
} }
@ -126,24 +133,24 @@ class LoginActivity : AppCompatActivity() {
private fun attemptLogin() { private fun attemptLogin() {
// Reset errors. // Reset errors.
mUrlView!!.error = null mUrlView.error = null
mLoginView!!.error = null mLoginView.error = null
mHTTPLoginView!!.error = null mHTTPLoginView.error = null
mPasswordView!!.error = null mPasswordView.error = null
mHTTPPasswordView!!.error = null mHTTPPasswordView.error = null
// Store values at the time of the login attempt. // Store values at the time of the login attempt.
val url = mUrlView!!.text.toString() val url = mUrlView.text.toString()
val login = mLoginView!!.text.toString() val login = mLoginView.text.toString()
val httpLogin = mHTTPLoginView!!.text.toString() val httpLogin = mHTTPLoginView.text.toString()
val password = mPasswordView!!.text.toString() val password = mPasswordView.text.toString()
val httpPassword = mHTTPPasswordView!!.text.toString() val httpPassword = mHTTPPasswordView.text.toString()
var cancel = false var cancel = false
var focusView: View? = null var focusView: View? = null
if (!isUrlValid(url)) { if (!url.isBaseUrlValid()) {
mUrlView!!.error = getString(R.string.login_url_problem) mUrlView.error = getString(R.string.login_url_problem)
focusView = mUrlView focusView = mUrlView
cancel = true cancel = true
inValidCount++ inValidCount++
@ -151,8 +158,10 @@ class LoginActivity : AppCompatActivity() {
val alertDialog = AlertDialog.Builder(this).create() val alertDialog = AlertDialog.Builder(this).create()
alertDialog.setTitle(getString(R.string.warning_wrong_url)) alertDialog.setTitle(getString(R.string.warning_wrong_url))
alertDialog.setMessage(getString(R.string.text_wrong_url)) alertDialog.setMessage(getString(R.string.text_wrong_url))
alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "OK", alertDialog.setButton(
{ dialog, _ -> dialog.dismiss() }) AlertDialog.BUTTON_NEUTRAL,
"OK",
{ dialog, _ -> dialog.dismiss() })
alertDialog.show() alertDialog.show()
inValidCount = 0 inValidCount = 0
} }
@ -160,24 +169,24 @@ class LoginActivity : AppCompatActivity() {
if (isWithLogin || isWithHTTPLogin) { if (isWithLogin || isWithHTTPLogin) {
if (TextUtils.isEmpty(password)) { if (TextUtils.isEmpty(password)) {
mPasswordView!!.error = getString(R.string.error_invalid_password) mPasswordView.error = getString(R.string.error_invalid_password)
focusView = mPasswordView focusView = mPasswordView
cancel = true cancel = true
} }
if (TextUtils.isEmpty(login)) { if (TextUtils.isEmpty(login)) {
mLoginView!!.error = getString(R.string.error_field_required) mLoginView.error = getString(R.string.error_field_required)
focusView = mLoginView focusView = mLoginView
cancel = true cancel = true
} }
} }
if (cancel) { if (cancel) {
focusView!!.requestFocus() focusView?.requestFocus()
} else { } else {
showProgress(true) showProgress(true)
val editor = settings!!.edit() val editor = settings.edit()
editor.putString("url", url) editor.putString("url", url)
editor.putString("login", login) editor.putString("login", login)
editor.putString("httpUserName", httpLogin) editor.putString("httpUserName", httpLogin)
@ -185,7 +194,7 @@ class LoginActivity : AppCompatActivity() {
editor.putString("httpPassword", httpPassword) editor.putString("httpPassword", httpPassword)
editor.apply() editor.apply()
val api = SelfossApi(this@LoginActivity) val api = SelfossApi(this, this@LoginActivity)
api.login().enqueue(object : Callback<SuccessResponse> { api.login().enqueue(object : Callback<SuccessResponse> {
private fun preferenceError() { private fun preferenceError() {
editor.remove("url") editor.remove("url")
@ -194,17 +203,17 @@ class LoginActivity : AppCompatActivity() {
editor.remove("password") editor.remove("password")
editor.remove("httpPassword") editor.remove("httpPassword")
editor.apply() editor.apply()
mUrlView!!.error = getString(R.string.wrong_infos) mUrlView.error = getString(R.string.wrong_infos)
mLoginView!!.error = getString(R.string.wrong_infos) mLoginView.error = getString(R.string.wrong_infos)
mPasswordView!!.error = getString(R.string.wrong_infos) mPasswordView.error = getString(R.string.wrong_infos)
mHTTPLoginView!!.error = getString(R.string.wrong_infos) mHTTPLoginView.error = getString(R.string.wrong_infos)
mHTTPPasswordView!!.error = getString(R.string.wrong_infos) mHTTPPasswordView.error = getString(R.string.wrong_infos)
showProgress(false) showProgress(false)
} }
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) { override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {
if (response.body() != null && response.body()!!.isSuccess) { if (response.body() != null && response.body()!!.isSuccess) {
mFirebaseAnalytics!!.logEvent(FirebaseAnalytics.Event.LOGIN, Bundle()) mFirebaseAnalytics.logEvent(FirebaseAnalytics.Event.LOGIN, Bundle())
goToMain() goToMain()
} else { } else {
preferenceError() preferenceError()
@ -224,26 +233,33 @@ class LoginActivity : AppCompatActivity() {
private fun showProgress(show: Boolean) { private fun showProgress(show: Boolean) {
val shortAnimTime = resources.getInteger(android.R.integer.config_shortAnimTime) val shortAnimTime = resources.getInteger(android.R.integer.config_shortAnimTime)
mLoginFormView!!.visibility = if (show) View.GONE else View.VISIBLE mLoginFormView.visibility = if (show) View.GONE else View.VISIBLE
mLoginFormView!!.animate().setDuration(shortAnimTime.toLong()).alpha( mLoginFormView
if (show) 0F else 1F).setListener(object : AnimatorListenerAdapter() { .animate()
override fun onAnimationEnd(animation: Animator) { .setDuration(shortAnimTime.toLong())
mLoginFormView!!.visibility = if (show) View.GONE else View.VISIBLE .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.visibility = if (show) View.VISIBLE else View.GONE
mProgressView!!.animate().setDuration(shortAnimTime.toLong()).alpha( mProgressView
if (show) 1F else 0F).setListener(object : AnimatorListenerAdapter() { .animate()
override fun onAnimationEnd(animation: Animator) { .setDuration(shortAnimTime.toLong())
mProgressView!!.visibility = if (show) View.VISIBLE else View.GONE .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 { override fun onCreateOptionsMenu(menu: Menu): Boolean {
val inflater = menuInflater menuInflater.inflate(R.menu.login_menu, menu)
inflater.inflate(R.menu.login_menu, menu)
return true return true
} }
@ -251,10 +267,10 @@ class LoginActivity : AppCompatActivity() {
when (item.itemId) { when (item.itemId) {
R.id.about -> { R.id.about -> {
LibsBuilder() LibsBuilder()
.withActivityStyle(Libs.ActivityStyle.LIGHT_DARK_TOOLBAR) .withActivityStyle(Libs.ActivityStyle.LIGHT_DARK_TOOLBAR)
.withAboutIconShown(true) .withAboutIconShown(true)
.withAboutVersionShown(true) .withAboutVersionShown(true)
.start(this) .start(this)
return true return true
} }
else -> return super.onOptionsItemSelected(item) else -> return super.onOptionsItemSelected(item)

View File

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

View File

@ -1,9 +1,20 @@
package apps.amine.bou.readerforselfoss 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.support.multidex.MultiDexApplication
import android.widget.ImageView
import com.anupcowkur.reservoir.Reservoir
import com.bumptech.glide.Glide
import com.crashlytics.android.Crashlytics import com.crashlytics.android.Crashlytics
import com.ftinc.scoop.Scoop
import com.github.stkent.amplify.tracking.Amplify import com.github.stkent.amplify.tracking.Amplify
import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader
import com.mikepenz.materialdrawer.util.DrawerImageLoader
import io.fabric.sdk.android.Fabric import io.fabric.sdk.android.Fabric
import java.io.IOException
class MyApp : MultiDexApplication() { class MyApp : MultiDexApplication() {
@ -12,9 +23,63 @@ class MyApp : MultiDexApplication() {
if (!BuildConfig.DEBUG) if (!BuildConfig.DEBUG)
Fabric.with(this, Crashlytics()) Fabric.with(this, Crashlytics())
initAmplify()
initCache()
initDrawerImageLoader()
initTheme()
}
private fun initAmplify() {
Amplify.initSharedInstance(this) Amplify.initSharedInstance(this)
.setFeedbackEmailAddress(getString(R.string.feedback_email)) .setFeedbackEmailAddress(getString(R.string.feedback_email))
.setAlwaysShow(BuildConfig.DEBUG) .applyAllDefaultRules()
.applyAllDefaultRules() }
private fun initCache() {
try {
Reservoir.init(this, 8192) //in bytes
} catch (e: IOException) {
//failure
}
}
private fun initDrawerImageLoader() {
DrawerImageLoader.init(object : AbstractDrawerImageLoader() {
override fun set(imageView: ImageView?, uri: Uri?, placeholder: Drawable?, tag: String?) {
Glide.with(imageView?.context).load(uri).placeholder(placeholder).into(imageView)
}
override fun cancel(imageView: ImageView?) {
Glide.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()
} }
} }

View File

@ -9,10 +9,7 @@ import android.view.ViewGroup
import android.widget.ImageButton import android.widget.ImageButton
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView 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 com.bumptech.glide.Glide
import org.sufficientlysecure.htmltextview.HtmlHttpImageGetter import org.sufficientlysecure.htmltextview.HtmlHttpImageGetter
import org.sufficientlysecure.htmltextview.HtmlTextView import org.sufficientlysecure.htmltextview.HtmlTextView
@ -21,82 +18,95 @@ import retrofit2.Callback
import retrofit2.Response import retrofit2.Response
import xyz.klinker.android.drag_dismiss.activity.DragDismissActivity 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.ftinc.scoop.Scoop
class ReaderActivity : DragDismissActivity() { class ReaderActivity : DragDismissActivity() {
private var mCustomTabActivityHelper: CustomTabActivityHelper? = null private lateinit var mCustomTabActivityHelper: CustomTabActivityHelper
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
mCustomTabActivityHelper!!.bindCustomTabsService(this) mCustomTabActivityHelper.bindCustomTabsService(this)
} }
override fun onStop() { override fun onStop() {
super.onStop() super.onStop()
mCustomTabActivityHelper!!.unbindCustomTabsService(this) mCustomTabActivityHelper.unbindCustomTabsService(this)
} }
override fun onCreateContent(inflater: LayoutInflater, parent: ViewGroup, savedInstanceState: Bundle?): View { override fun onCreateContent(inflater: LayoutInflater, parent: ViewGroup, savedInstanceState: Bundle?): View {
Scoop.getInstance().apply(this)
val v = inflater.inflate(R.layout.activity_reader, parent, false) val v = inflater.inflate(R.layout.activity_reader, parent, false)
showProgressBar() showProgressBar()
val image = v.findViewById(R.id.imageView) as ImageView val image: ImageView = v.findViewById(R.id.imageView)
val source = v.findViewById(R.id.source) as TextView val source: TextView = v.findViewById(R.id.source)
val title = v.findViewById(R.id.title) as TextView val title: TextView = v.findViewById(R.id.title)
val content = v.findViewById(R.id.content) as HtmlTextView val content: HtmlTextView = v.findViewById(R.id.content)
val url = intent.getStringExtra("url") val url = intent.getStringExtra("url")
val parser = MercuryApi(getString(R.string.mercury)) val parser = MercuryApi(getString(R.string.mercury))
val browserBtn: ImageButton = v.findViewById(R.id.browserBtn) as ImageButton val browserBtn: ImageButton = v.findViewById(R.id.browserBtn)
val shareBtn: ImageButton = v.findViewById(R.id.shareBtn) as ImageButton val shareBtn: ImageButton = v.findViewById(R.id.shareBtn)
val customTabsIntent = buildCustomTabsIntent(this@ReaderActivity) val customTabsIntent = this@ReaderActivity.buildCustomTabsIntent()
mCustomTabActivityHelper = CustomTabActivityHelper() mCustomTabActivityHelper = CustomTabActivityHelper()
mCustomTabActivityHelper!!.bindCustomTabsService(this) mCustomTabActivityHelper.bindCustomTabsService(this)
parser.parseUrl(url).enqueue(object : Callback<ParsedContent> { parser.parseUrl(url).enqueue(object : Callback<ParsedContent> {
override fun onResponse(call: Call<ParsedContent>, response: Response<ParsedContent>) { override fun onResponse(call: Call<ParsedContent>, response: Response<ParsedContent>) {
if (response.body() != null && response.body()!!.content.isNotEmpty()) { if (response.body() != null && response.body()!!.content != null && response.body()!!.content.isNotEmpty()) {
source.text = response.body()!!.domain source.text = response.body()!!.domain
title.text = response.body()!!.title title.text = response.body()!!.title
if (response.body()!!.content != null && !response.body()!!.content.isEmpty()) if (response.body()!!.content != null && !response.body()!!.content.isEmpty()) {
content.setHtml(response.body()!!.content, HtmlHttpImageGetter(content, null, true)) 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()) 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)
.load(response.body()!!.lead_image_url)
.asBitmap()
.fitCenter()
.into(image)
shareBtn.setOnClickListener { shareBtn.setOnClickListener {
val sendIntent = Intent() this@ReaderActivity.shareLink(response.body()!!.url)
sendIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
sendIntent.action = Intent.ACTION_SEND
sendIntent.putExtra(Intent.EXTRA_TEXT, response.body()!!.url)
sendIntent.type = "text/plain"
startActivity(Intent.createChooser(sendIntent, getString(R.string.share)).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
} }
browserBtn.setOnClickListener { browserBtn.setOnClickListener {
val intent = Intent(Intent.ACTION_VIEW) this@ReaderActivity.openItemUrl(
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK response.body()!!.url,
intent.data = Uri.parse(response.body()!!.url) customTabsIntent,
startActivity(intent) false,
false,
this@ReaderActivity)
} }
hideProgressBar() hideProgressBar()
} else { } else openInBrowserAfterFailing()
errorAfterMercuryCall()
}
} }
override fun onFailure(call: Call<ParsedContent>, t: Throwable) { override fun onFailure(call: Call<ParsedContent>, t: Throwable) = openInBrowserAfterFailing()
errorAfterMercuryCall()
}
private fun errorAfterMercuryCall() { private fun openInBrowserAfterFailing() {
CustomTabActivityHelper.openCustomTab(this@ReaderActivity, customTabsIntent, Uri.parse(url) this@ReaderActivity.openItemUrl(
) { _, uri -> url,
val intent = Intent(Intent.ACTION_VIEW, uri) customTabsIntent,
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK true,
startActivity(intent) false,
} this@ReaderActivity
)
finish() finish()
} }
}) })

View File

@ -5,29 +5,38 @@ import android.os.Bundle
import android.support.v7.app.AppCompatActivity import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView import android.support.v7.widget.RecyclerView
import android.support.v7.widget.Toolbar
import android.widget.Toast 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 com.melnykov.fab.FloatingActionButton
import retrofit2.Call import retrofit2.Call
import retrofit2.Callback import retrofit2.Callback
import retrofit2.Response 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() { class SourcesActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
Scoop.getInstance().apply(this)
setContentView(R.layout.activity_sources) setContentView(R.layout.activity_sources)
val toolbar: Toolbar = findViewById(R.id.toolbar)
setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
} }
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
val mFab = findViewById(R.id.fab) as FloatingActionButton val mFab: FloatingActionButton = findViewById(R.id.fab)
val mRecyclerView = findViewById(R.id.activity_sources) as RecyclerView val mRecyclerView: RecyclerView = findViewById(R.id.activity_sources)
val mLayoutManager = LinearLayoutManager(this) val mLayoutManager = LinearLayoutManager(this)
val api = SelfossApi(this) val api = SelfossApi(this, this@SourcesActivity)
var items: ArrayList<Sources> = ArrayList() var items: ArrayList<Sources> = ArrayList()
mFab.attachToRecyclerView(mRecyclerView) mFab.attachToRecyclerView(mRecyclerView)

View File

@ -1,15 +1,13 @@
package apps.amine.bou.readerforselfoss.adapters package apps.amine.bou.readerforselfoss.adapters
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.Color import android.graphics.Color
import android.net.Uri
import android.support.constraint.ConstraintLayout import android.support.constraint.ConstraintLayout
import android.support.design.widget.Snackbar import android.support.design.widget.Snackbar
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory
import android.support.v7.widget.CardView
import android.support.v7.widget.RecyclerView import android.support.v7.widget.RecyclerView
import android.text.Html import android.text.Html
import android.text.format.DateUtils import android.text.format.DateUtils
@ -20,13 +18,7 @@ import android.widget.ImageView
import android.widget.ImageView.ScaleType import android.widget.ImageView.ScaleType
import android.widget.TextView import android.widget.TextView
import android.widget.Toast 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.TextDrawable
import com.amulyakhare.textdrawable.util.ColorGenerator import com.amulyakhare.textdrawable.util.ColorGenerator
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
@ -40,11 +32,23 @@ import java.text.ParseException
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* 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
class ItemCardAdapter(private val app: Activity, private val items: ArrayList<Item>, private val api: SelfossApi, class ItemCardAdapter(private val app: Activity,
private val helper: CustomTabActivityHelper, private val internalBrowser: Boolean, private val items: ArrayList<Item>,
private val articleViewer: Boolean, private val fullHeightCards: Boolean) : RecyclerView.Adapter<ItemCardAdapter.ViewHolder>() { private val api: SelfossApi,
private val c: Context = app.applicationContext private val helper: CustomTabActivityHelper,
private val internalBrowser: Boolean,
private val articleViewer: Boolean,
private val fullHeightCards: Boolean,
private val appColors: AppColors) : RecyclerView.Adapter<ItemCardAdapter.ViewHolder>() {
private val c: Context = app.baseContext
private val generator: ColorGenerator = ColorGenerator.MATERIAL private val generator: ColorGenerator = ColorGenerator.MATERIAL
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
@ -56,60 +60,37 @@ class ItemCardAdapter(private val app: Activity, private val items: ArrayList<It
val itm = items[position] val itm = items[position]
holder.saveBtn!!.isLiked = itm.starred holder.saveBtn.isLiked = itm.starred
holder.title!!.text = Html.fromHtml(itm.title) holder.title.text = Html.fromHtml(itm.title)
var sourceAndDate = itm.sourcetitle holder.sourceTitleAndDate.text = itm.sourceAndDateText()
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
if (itm.getThumbnail(c).isEmpty()) { if (itm.getThumbnail(c).isEmpty()) {
Glide.clear(holder.itemImage) Glide.clear(holder.itemImage)
holder.itemImage!!.setImageDrawable(null) holder.itemImage.setImageDrawable(null)
} else { } else {
if (fullHeightCards) { if (fullHeightCards) {
Glide.with(c).load(itm.getThumbnail(c)).asBitmap().fitCenter().into(holder.itemImage) c.bitmapFitCenter(itm.getThumbnail(c), holder.itemImage)
} else { } else {
Glide.with(c).load(itm.getThumbnail(c)).asBitmap().centerCrop().into(holder.itemImage) c.bitmapCenterCrop(itm.getThumbnail(c), holder.itemImage)
} }
} }
val fHolder = holder val fHolder = holder
if (itm.getIcon(c).isEmpty()) { if (itm.getIcon(c).isEmpty()) {
val color = generator.getColor(itm.sourcetitle) val color = generator.getColor(itm.sourcetitle)
val textDrawable = StringBuilder()
for (s in itm.sourcetitle.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) {
textDrawable.append(s[0])
}
val builder = TextDrawable.builder().round() val drawable =
TextDrawable
val drawable = builder.build(textDrawable.toString(), color) .builder()
holder.sourceImage!!.setImageDrawable(drawable) .round()
.build(itm.sourcetitle.toTextDrawableString(), color)
holder.sourceImage.setImageDrawable(drawable)
} else { } else {
c.circularBitmapDrawable(itm.getIcon(c), holder.sourceImage)
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)
}
})
} }
holder.saveBtn!!.isLiked = itm.starred holder.saveBtn.isLiked = itm.starred
} }
override fun getItemCount(): Int { override fun getItemCount(): Int {
@ -135,7 +116,7 @@ class ItemCardAdapter(private val app: Activity, private val items: ArrayList<It
} }
val view = s.view 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) tv.setTextColor(Color.WHITE)
s.show() s.show()
} }
@ -163,41 +144,42 @@ class ItemCardAdapter(private val app: Activity, private val items: ArrayList<It
} }
inner class ViewHolder(val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) { inner class ViewHolder(val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) {
var saveBtn: LikeButton? = null lateinit var saveBtn: LikeButton
var browserBtn: ImageButton? = null lateinit var browserBtn: ImageButton
var shareBtn: ImageButton? = null lateinit var shareBtn: ImageButton
var itemImage: ImageView? = null lateinit var itemImage: ImageView
var sourceImage: ImageView? = null lateinit var sourceImage: ImageView
var title: TextView? = null lateinit var title: TextView
var sourceTitleAndDate: TextView? = null lateinit var sourceTitleAndDate: TextView
init { init {
(mView.findViewById<CardView>(R.id.card)).setCardBackgroundColor(appColors.cardBackground)
handleClickListeners() handleClickListeners()
handleCustomTabActions() handleCustomTabActions()
} }
private fun handleClickListeners() { private fun handleClickListeners() {
sourceImage = mView.findViewById(R.id.sourceImage) as ImageView sourceImage = mView.findViewById(R.id.sourceImage)
itemImage = mView.findViewById(R.id.itemImage) as ImageView itemImage = mView.findViewById(R.id.itemImage)
title = mView.findViewById(R.id.title) as TextView title = mView.findViewById(R.id.title)
sourceTitleAndDate = mView.findViewById(R.id.sourceTitleAndDate) as TextView sourceTitleAndDate = mView.findViewById(R.id.sourceTitleAndDate)
saveBtn = mView.findViewById(R.id.favButton) as LikeButton saveBtn = mView.findViewById(R.id.favButton)
shareBtn = mView.findViewById(R.id.shareBtn) as ImageButton shareBtn = mView.findViewById(R.id.shareBtn)
browserBtn = mView.findViewById(R.id.browserBtn) as ImageButton browserBtn = mView.findViewById(R.id.browserBtn)
if (!fullHeightCards) { if (!fullHeightCards) {
itemImage!!.maxHeight = c.resources.getDimension(R.dimen.card_image_max_height).toInt() itemImage.maxHeight = c.resources.getDimension(R.dimen.card_image_max_height).toInt()
itemImage!!.scaleType = ScaleType.CENTER_CROP itemImage.scaleType = ScaleType.CENTER_CROP
} }
saveBtn!!.setOnLikeListener(object : OnLikeListener { saveBtn.setOnLikeListener(object : OnLikeListener {
override fun liked(likeButton: LikeButton) { override fun liked(likeButton: LikeButton) {
val (id) = items[adapterPosition] val (id) = items[adapterPosition]
api.starrItem(id).enqueue(object : Callback<SuccessResponse> { api.starrItem(id).enqueue(object : Callback<SuccessResponse> {
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {} override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {}
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) { 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() Toast.makeText(c, R.string.cant_mark_favortie, Toast.LENGTH_SHORT).show()
} }
}) })
@ -209,43 +191,32 @@ class ItemCardAdapter(private val app: Activity, private val items: ArrayList<It
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {} override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {}
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) { 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() Toast.makeText(c, R.string.cant_unmark_favortie, Toast.LENGTH_SHORT).show()
} }
}) })
} }
}) })
shareBtn!!.setOnClickListener { shareBtn.setOnClickListener {
val i = items[adapterPosition] c.shareLink(items[adapterPosition].getLinkDecoded())
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))
} }
browserBtn!!.setOnClickListener { browserBtn.setOnClickListener {
val i = items[adapterPosition] c.openInBrowser(items[adapterPosition])
val intent = Intent(Intent.ACTION_VIEW)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
intent.data = Uri.parse(i.getLinkDecoded())
c.startActivity(intent)
} }
} }
private fun handleCustomTabActions() { private fun handleCustomTabActions() {
val customTabsIntent = buildCustomTabsIntent(c) val customTabsIntent = c.buildCustomTabsIntent()
helper.bindCustomTabsService(app) helper.bindCustomTabsService(app)
mView.setOnClickListener { mView.setOnClickListener {
openItemUrl(items[adapterPosition], c.openItemUrl(items[adapterPosition].getLinkDecoded(),
customTabsIntent, customTabsIntent,
internalBrowser, internalBrowser,
articleViewer, articleViewer,
app, app)
c)
} }
} }
} }

View File

@ -18,13 +18,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.* 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.TextDrawable
import com.amulyakhare.textdrawable.util.ColorGenerator import com.amulyakhare.textdrawable.util.ColorGenerator
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
@ -38,12 +32,24 @@ import java.text.ParseException
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* 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
class ItemListAdapter(private val app: Activity, private val items: ArrayList<Item>, private val api: SelfossApi,
private val helper: CustomTabActivityHelper, private val clickBehavior: Boolean, class ItemListAdapter(private val app: Activity,
private val internalBrowser: Boolean, private val articleViewer: Boolean) : RecyclerView.Adapter<ItemListAdapter.ViewHolder>() { 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>() {
private val generator: ColorGenerator = ColorGenerator.MATERIAL 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)) private val bars: ArrayList<Boolean> = ArrayList(Collections.nCopies(items.size + 1, false))
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
@ -55,41 +61,27 @@ class ItemListAdapter(private val app: Activity, private val items: ArrayList<It
val itm = items[position] val itm = items[position]
holder.saveBtn!!.isLiked = itm.starred holder.saveBtn.isLiked = itm.starred
holder.title!!.text = Html.fromHtml(itm.title) holder.title.text = Html.fromHtml(itm.title)
var sourceAndDate = itm.sourcetitle holder.sourceTitleAndDate.text = itm.sourceAndDateText()
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
if (itm.getThumbnail(c).isEmpty()) { if (itm.getThumbnail(c).isEmpty()) {
val sizeInInt = 46 val sizeInInt = 46
val sizeInDp = TypedValue.applyDimension( val sizeInDp = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, sizeInInt.toFloat(), c.resources TypedValue.COMPLEX_UNIT_DIP, sizeInInt.toFloat(), c.resources
.displayMetrics).toInt() .displayMetrics).toInt()
val marginInInt = 16 val marginInInt = 16
val marginInDp = TypedValue.applyDimension( val marginInDp = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, marginInInt.toFloat(), c.resources TypedValue.COMPLEX_UNIT_DIP, marginInInt.toFloat(), c.resources
.displayMetrics).toInt() .displayMetrics).toInt()
val params = holder.sourceImage!!.layoutParams as ViewGroup.MarginLayoutParams val params = holder.sourceImage.layoutParams as ViewGroup.MarginLayoutParams
params.height = sizeInDp params.height = sizeInDp
params.width = sizeInDp params.width = sizeInDp
params.setMargins(marginInDp, 0, 0, 0) params.setMargins(marginInDp, 0, 0, 0)
holder.sourceImage!!.layoutParams = params holder.sourceImage.layoutParams = params
if (itm.getIcon(c).isEmpty()) { if (itm.getIcon(c).isEmpty()) {
val color = generator.getColor(itm.sourcetitle) val color = generator.getColor(itm.sourcetitle)
@ -101,34 +93,20 @@ class ItemListAdapter(private val app: Activity, private val items: ArrayList<It
val builder = TextDrawable.builder().round() val builder = TextDrawable.builder().round()
val drawable = builder.build(textDrawable.toString(), color) val drawable = builder.build(textDrawable.toString(), color)
holder.sourceImage!!.setImageDrawable(drawable) holder.sourceImage.setImageDrawable(drawable)
} else { } else {
c.circularBitmapDrawable(itm.getIcon(c), holder.sourceImage)
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)
}
})
} }
} else { } else {
Glide.with(c).load(itm.getThumbnail(c)).asBitmap().centerCrop().into(holder.sourceImage) c.bitmapCenterCrop(itm.getThumbnail(c), holder.sourceImage)
} }
if (bars[position]) { if (bars[position]) holder.actionBar.visibility = View.VISIBLE else holder.actionBar.visibility = View.GONE
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 { override fun getItemCount(): Int = items.size
return items.size
}
private fun doUnmark(i: Item, position: Int) { private fun doUnmark(i: Item, position: Int) {
@ -150,7 +128,7 @@ class ItemListAdapter(private val app: Activity, private val items: ArrayList<It
} }
val view = s.view 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) tv.setTextColor(Color.WHITE)
s.show() s.show()
} }
@ -178,13 +156,13 @@ class ItemListAdapter(private val app: Activity, private val items: ArrayList<It
} }
inner class ViewHolder(val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) { inner class ViewHolder(val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) {
var saveBtn: LikeButton? = null lateinit var saveBtn: LikeButton
var browserBtn: ImageButton? = null lateinit var browserBtn: ImageButton
var shareBtn: ImageButton? = null lateinit var shareBtn: ImageButton
var actionBar: RelativeLayout? = null lateinit var actionBar: RelativeLayout
var sourceImage: ImageView? = null lateinit var sourceImage: ImageView
var title: TextView? = null lateinit var title: TextView
var sourceTitleAndDate: TextView? = null lateinit var sourceTitleAndDate: TextView
init { init {
handleClickListeners() handleClickListeners()
@ -192,23 +170,23 @@ class ItemListAdapter(private val app: Activity, private val items: ArrayList<It
} }
private fun handleClickListeners() { private fun handleClickListeners() {
actionBar = mView.findViewById(R.id.actionBar) as RelativeLayout actionBar = mView.findViewById(R.id.actionBar)
sourceImage = mView.findViewById(R.id.itemImage) as ImageView sourceImage = mView.findViewById(R.id.itemImage)
title = mView.findViewById(R.id.title) as TextView title = mView.findViewById(R.id.title)
sourceTitleAndDate = mView.findViewById(R.id.sourceTitleAndDate) as TextView sourceTitleAndDate = mView.findViewById(R.id.sourceTitleAndDate)
saveBtn = mView.findViewById(R.id.favButton) as LikeButton saveBtn = mView.findViewById(R.id.favButton)
shareBtn = mView.findViewById(R.id.shareBtn) as ImageButton shareBtn = mView.findViewById(R.id.shareBtn)
browserBtn = mView.findViewById(R.id.browserBtn) as ImageButton browserBtn = mView.findViewById(R.id.browserBtn)
saveBtn!!.setOnLikeListener(object : OnLikeListener { saveBtn.setOnLikeListener(object : OnLikeListener {
override fun liked(likeButton: LikeButton) { override fun liked(likeButton: LikeButton) {
val (id) = items[adapterPosition] val (id) = items[adapterPosition]
api.starrItem(id).enqueue(object : Callback<SuccessResponse> { api.starrItem(id).enqueue(object : Callback<SuccessResponse> {
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {} override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {}
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) { 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() Toast.makeText(c, R.string.cant_mark_favortie, Toast.LENGTH_SHORT).show()
} }
}) })
@ -220,46 +198,36 @@ class ItemListAdapter(private val app: Activity, private val items: ArrayList<It
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {} override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {}
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) { 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() Toast.makeText(c, R.string.cant_unmark_favortie, Toast.LENGTH_SHORT).show()
} }
}) })
} }
}) })
shareBtn!!.setOnClickListener { shareBtn.setOnClickListener {
val i = items[adapterPosition] c.shareLink(items[adapterPosition].getLinkDecoded())
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))
} }
browserBtn!!.setOnClickListener { browserBtn.setOnClickListener {
val i = items[adapterPosition] c.openInBrowser(items[adapterPosition])
val intent = Intent(Intent.ACTION_VIEW)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
intent.data = Uri.parse(i.getLinkDecoded())
c.startActivity(intent)
} }
} }
private fun handleCustomTabActions() { private fun handleCustomTabActions() {
val customTabsIntent = buildCustomTabsIntent(c) val customTabsIntent = c.buildCustomTabsIntent()
helper.bindCustomTabsService(app) helper.bindCustomTabsService(app)
if (!clickBehavior) { if (!clickBehavior) {
mView.setOnClickListener { mView.setOnClickListener {
openItemUrl(items[adapterPosition], c.openItemUrl(items[adapterPosition].getLinkDecoded(),
customTabsIntent, customTabsIntent,
internalBrowser, internalBrowser,
articleViewer, articleViewer,
app, app)
c)
} }
mView.setOnLongClickListener { mView.setOnLongClickListener {
actionBarShowHide() actionBarShowHide()
@ -268,12 +236,11 @@ class ItemListAdapter(private val app: Activity, private val items: ArrayList<It
} else { } else {
mView.setOnClickListener { actionBarShowHide() } mView.setOnClickListener { actionBarShowHide() }
mView.setOnLongClickListener { mView.setOnLongClickListener {
openItemUrl(items[adapterPosition], c.openItemUrl(items[adapterPosition].getLinkDecoded(),
customTabsIntent, customTabsIntent,
internalBrowser, internalBrowser,
articleViewer, articleViewer,
app, app)
c)
true true
} }
} }
@ -281,10 +248,7 @@ class ItemListAdapter(private val app: Activity, private val items: ArrayList<It
private fun actionBarShowHide() { private fun actionBarShowHide() {
bars[adapterPosition] = true bars[adapterPosition] = true
if (actionBar!!.visibility == View.GONE) if (actionBar.visibility == View.GONE) actionBar.visibility = View.VISIBLE else 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.app.Activity
import android.content.Context import android.content.Context
import android.graphics.Bitmap
import android.support.constraint.ConstraintLayout import android.support.constraint.ConstraintLayout
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory
import android.support.v7.widget.RecyclerView import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
@ -12,19 +10,24 @@ import android.widget.Button
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import android.widget.Toast 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.TextDrawable
import com.amulyakhare.textdrawable.util.ColorGenerator import com.amulyakhare.textdrawable.util.ColorGenerator
import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.BitmapImageViewTarget
import retrofit2.Call import retrofit2.Call
import retrofit2.Callback import retrofit2.Callback
import retrofit2.Response 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.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 c: Context = app.baseContext
private val generator: ColorGenerator = ColorGenerator.MATERIAL 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) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val itm = items[position] val itm = items[position]
val fHolder = holder
if (itm.getIcon(c).isEmpty()) { if (itm.getIcon(c).isEmpty()) {
val color = generator.getColor(itm.title) 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 =
TextDrawable
val drawable = builder.build(textDrawable.toString(), color) .builder()
holder.sourceImage!!.setImageDrawable(drawable) .round()
.build(itm.title.toTextDrawableString(), color)
holder.sourceImage.setImageDrawable(drawable)
} else { } else {
Glide.with(c).load(itm.getIcon(c)).asBitmap().centerCrop().into(object : BitmapImageViewTarget(holder.sourceImage) { c.circularBitmapDrawable(itm.getIcon(c), holder.sourceImage)
override fun setResource(resource: Bitmap) {
val circularBitmapDrawable = RoundedBitmapDrawableFactory.create(c.resources, resource)
circularBitmapDrawable.isCircular = true
fHolder.sourceImage!!.setImageDrawable(circularBitmapDrawable)
}
})
} }
holder.sourceTitle!!.text = itm.title holder.sourceTitle.text = itm.title
} }
override fun getItemCount(): Int { 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) { inner class ViewHolder(internal val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) {
var sourceImage: ImageView? = null lateinit var sourceImage: ImageView
var sourceTitle: TextView? = null lateinit var sourceTitle: TextView
init { init {
@ -75,10 +69,10 @@ class SourcesListAdapter(private val app: Activity, private val items: ArrayList
} }
private fun handleClickListeners() { private fun handleClickListeners() {
sourceImage = mView.findViewById(R.id.itemImage) as ImageView sourceImage = mView.findViewById(R.id.itemImage)
sourceTitle = mView.findViewById(R.id.sourceTitle) as TextView 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 { deleteBtn.setOnClickListener {
val (id) = items[adapterPosition] val (id) = items[adapterPosition]
@ -89,12 +83,12 @@ class SourcesListAdapter(private val app: Activity, private val items: ArrayList
notifyItemRemoved(adapterPosition) notifyItemRemoved(adapterPosition)
notifyItemRangeChanged(adapterPosition, itemCount) notifyItemRangeChanged(adapterPosition, itemCount)
} else { } 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) { 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 package apps.amine.bou.readerforselfoss.api.mercury
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor import okhttp3.logging.HttpLoggingInterceptor
@ -9,6 +8,7 @@ import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory import retrofit2.converter.gson.GsonConverterFactory
class MercuryApi(private val key: String) { class MercuryApi(private val key: String) {
private val service: MercuryService private val service: MercuryService
@ -21,8 +21,13 @@ class MercuryApi(private val key: String) {
val gson = GsonBuilder() val gson = GsonBuilder()
.setLenient() .setLenient()
.create() .create()
val retrofit = Retrofit.Builder().baseUrl("https://mercury.postlight.com").client(client) val retrofit =
.addConverterFactory(GsonConverterFactory.create(gson)).build() Retrofit
.Builder()
.baseUrl("https://mercury.postlight.com")
.client(client)
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
service = retrofit.create(MercuryService::class.java) service = retrofit.create(MercuryService::class.java)
} }

View File

@ -4,6 +4,7 @@ import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
class ParsedContent(val title: String, class ParsedContent(val title: String,
val content: String, val content: String,
val date_published: String, val date_published: String,
@ -24,17 +25,17 @@ class ParsedContent(val title: String,
} }
constructor(source: Parcel) : this( constructor(source: Parcel) : this(
title = source.readString(), title = source.readString(),
content = source.readString(), content = source.readString(),
date_published = source.readString(), date_published = source.readString(),
lead_image_url = source.readString(), lead_image_url = source.readString(),
dek = source.readString(), dek = source.readString(),
url = source.readString(), url = source.readString(),
domain = source.readString(), domain = source.readString(),
excerpt = source.readString(), excerpt = source.readString(),
total_pages = source.readInt(), total_pages = source.readInt(),
rendered_pages = source.readInt(), rendered_pages = source.readInt(),
next_page_url = source.readString() next_page_url = source.readString()
) )
override fun describeContents() = 0 override fun describeContents() = 0

View File

@ -7,6 +7,7 @@ import retrofit2.http.Header
import retrofit2.http.Query import retrofit2.http.Query
interface MercuryService { interface MercuryService {
@GET("parser") @GET("parser")
fun parseUrl(@Query("url") url: String, @Header("x-api-key") key: String): Call<ParsedContent> 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 package apps.amine.bou.readerforselfoss.api.selfoss
import java.lang.reflect.Type
import com.google.gson.JsonParseException import com.google.gson.JsonParseException
import com.google.gson.JsonDeserializationContext import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonElement import com.google.gson.JsonElement
import com.google.gson.JsonDeserializer import com.google.gson.JsonDeserializer
import java.lang.reflect.Type
internal class BooleanTypeAdapter : JsonDeserializer<Boolean> { internal class BooleanTypeAdapter : JsonDeserializer<Boolean> {

View File

@ -1,8 +1,14 @@
package apps.amine.bou.readerforselfoss.api.selfoss package apps.amine.bou.readerforselfoss.api.selfoss
import android.app.Activity
import android.content.Context import android.content.Context
import apps.amine.bou.readerforselfoss.utils.Config import android.content.Intent
import android.support.v7.app.AlertDialog
import android.widget.Toast
import apps.amine.bou.readerforselfoss.LoginActivity
import apps.amine.bou.readerforselfoss.R
import java.util.concurrent.ConcurrentHashMap
import com.burgstaller.okhttp.AuthenticationCacheInterceptor import com.burgstaller.okhttp.AuthenticationCacheInterceptor
import com.burgstaller.okhttp.CachingAuthenticatorDecorator import com.burgstaller.okhttp.CachingAuthenticatorDecorator
import com.burgstaller.okhttp.DispatchingAuthenticator import com.burgstaller.okhttp.DispatchingAuthenticator
@ -12,99 +18,97 @@ import com.burgstaller.okhttp.digest.Credentials
import com.burgstaller.okhttp.digest.DigestAuthenticator import com.burgstaller.okhttp.digest.DigestAuthenticator
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Call import retrofit2.Call
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.ConcurrentHashMap
import apps.amine.bou.readerforselfoss.utils.Config
class SelfossApi(c: Context) { // codebeat:disable[ARITY,TOO_MANY_FUNCTIONS]
class SelfossApi(c: Context, callingActivity: Activity) {
private val service: SelfossService private lateinit var service: SelfossService
private val config: Config = Config(c) private val config: Config = Config(c)
private val userName: String private val userName: String
private val password: String private val password: String
init { fun Credentials.createAuthenticator(): DispatchingAuthenticator =
DispatchingAuthenticator.Builder()
.with("digest", DigestAuthenticator(this))
.with("basic", BasicAuthenticator(this))
.build()
val interceptor = HttpLoggingInterceptor() fun DispatchingAuthenticator.getHttpClien(): OkHttpClient {
interceptor.level = HttpLoggingInterceptor.Level.BODY
val httpBuilder = OkHttpClient.Builder()
val authCache = ConcurrentHashMap<String, CachingAuthenticator>() val authCache = ConcurrentHashMap<String, CachingAuthenticator>()
return OkHttpClient
val httpUserName = config.httpUserLogin .Builder()
val httpPassword = config.httpUserPassword .authenticator(CachingAuthenticatorDecorator(this, authCache))
.addInterceptor(AuthenticationCacheInterceptor(authCache))
val credentials = Credentials(httpUserName, httpPassword) .build()
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()
val builder = GsonBuilder() init {
builder.registerTypeAdapter(Boolean::class.javaPrimitiveType, BooleanTypeAdapter()) 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() .setLenient()
.create() .create()
userName = config.userLogin
password = config.userPassword try {
val retrofit = Retrofit.Builder().baseUrl(config.baseUrl).client(client) val retrofit =
.addConverterFactory(GsonConverterFactory.create(gson)).build() Retrofit
service = retrofit.create(SelfossService::class.java) .Builder()
.baseUrl(config.baseUrl)
.client(authenticator.getHttpClien())
.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> { fun login(): Call<SuccessResponse> =
return service.loginToSelfoss(config.userLogin, config.userPassword) service.loginToSelfoss(config.userLogin, config.userPassword)
}
val readItems: Call<List<Item>> fun readItems(tag: String?, sourceId: Long?, search: String?): Call<List<Item>> =
get() = getItems("read") getItems("read", tag, sourceId, search)
val unreadItems: Call<List<Item>> fun newItems(tag: String?, sourceId: Long?, search: String?): Call<List<Item>> =
get() = getItems("unread") getItems("unread", tag, sourceId, search)
val starredItems: Call<List<Item>> fun starredItems(tag: String?, sourceId: Long?, search: String?): Call<List<Item>> =
get() = getItems("starred") getItems("starred", tag, sourceId, search)
private fun getItems(type: String): Call<List<Item>> { private fun getItems(type: String, tag: String?, sourceId: Long?, search: String?): Call<List<Item>> =
return service.getItems(type, userName, password) service.getItems(type, tag, sourceId, search, userName, password)
}
fun markItem(itemId: String): Call<SuccessResponse> { fun markItem(itemId: String): Call<SuccessResponse> =
return service.markAsRead(itemId, userName, password) service.markAsRead(itemId, userName, password)
}
fun unmarkItem(itemId: String): Call<SuccessResponse> { fun unmarkItem(itemId: String): Call<SuccessResponse> =
return service.unmarkAsRead(itemId, userName, password) service.unmarkAsRead(itemId, userName, password)
}
fun readAll(ids: List<String>): Call<SuccessResponse> { fun readAll(ids: List<String>): Call<SuccessResponse> =
return service.markAllAsRead(ids, userName, password) service.markAllAsRead(ids, userName, password)
}
fun starrItem(itemId: String): Call<SuccessResponse> { fun starrItem(itemId: String): Call<SuccessResponse> =
return service.starr(itemId, userName, password) service.starr(itemId, userName, password)
}
fun unstarrItem(itemId: String): Call<SuccessResponse> =
fun unstarrItem(itemId: String): Call<SuccessResponse> { service.unstarr(itemId, userName, password)
return service.unstarr(itemId, userName, password)
}
val stats: Call<Stats> val stats: Call<Stats>
get() = service.stats(userName, password) get() = service.stats(userName, password)
@ -112,23 +116,21 @@ class SelfossApi(c: Context) {
val tags: Call<List<Tag>> val tags: Call<List<Tag>>
get() = service.tags(userName, password) get() = service.tags(userName, password)
fun update(): Call<String> { fun update(): Call<String> =
return service.update(userName, password) service.update(userName, password)
}
val sources: Call<List<Sources>> val sources: Call<List<Sources>>
get() = service.sources(userName, password) get() = service.sources(userName, password)
fun deleteSource(id: String): Call<SuccessResponse> { fun deleteSource(id: String): Call<SuccessResponse> =
return service.deleteSource(id, userName, password) service.deleteSource(id, userName, password)
}
fun spouts(): Call<Map<String, Spout>> { fun spouts(): Call<Map<String, Spout>> =
return service.spouts(userName, password) service.spouts(userName, password)
}
fun createSource(title: String, url: String, spout: String, tags: String, filter: String): Call<SuccessResponse> { fun createSource(title: String, url: String, spout: String, tags: String, filter: String): Call<SuccessResponse> =
return service.createSource(title, url, spout, tags, filter, userName, password) service.createSource(title, url, spout, tags, filter, userName, password)
}
} }
// codebeat:enable[ARITY,TOO_MANY_FUNCTIONS]

View File

@ -4,15 +4,17 @@ import android.content.Context
import android.net.Uri import android.net.Uri
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import apps.amine.bou.readerforselfoss.utils.Config import apps.amine.bou.readerforselfoss.utils.Config
import apps.amine.bou.readerforselfoss.utils.isEmptyOrNullOrNullString import apps.amine.bou.readerforselfoss.utils.isEmptyOrNullOrNullString
private fun constructUrl(config: Config?, path: String, file: String): String { private fun constructUrl(config: Config?, path: String, file: String): String {
val baseUriBuilder = Uri.parse(config!!.baseUrl).buildUpon() val baseUriBuilder = Uri.parse(config!!.baseUrl).buildUpon()
baseUriBuilder.appendPath(path).appendPath(file) baseUriBuilder.appendPath(path).appendPath(file)
return if (isEmptyOrNullOrNullString(file)) "" return if (file.isEmptyOrNullOrNullString()) ""
else baseUriBuilder.toString() else baseUriBuilder.toString()
} }
@ -65,15 +67,15 @@ data class Item(val id: String,
} }
constructor(source: Parcel) : this( constructor(source: Parcel) : this(
id = source.readString(), id = source.readString(),
datetime = source.readString(), datetime = source.readString(),
title = source.readString(), title = source.readString(),
unread = 0.toByte() != source.readByte(), unread = 0.toByte() != source.readByte(),
starred = 0.toByte() != source.readByte(), starred = 0.toByte() != source.readByte(),
thumbnail = source.readString(), thumbnail = source.readString(),
icon = source.readString(), icon = source.readString(),
link = source.readString(), link = source.readString(),
sourcetitle = source.readString() sourcetitle = source.readString()
) )
override fun describeContents() = 0 override fun describeContents() = 0

View File

@ -1,6 +1,5 @@
package apps.amine.bou.readerforselfoss.api.selfoss package apps.amine.bou.readerforselfoss.api.selfoss
import retrofit2.Call import retrofit2.Call
import retrofit2.http.DELETE import retrofit2.http.DELETE
import retrofit2.http.Field import retrofit2.http.Field
@ -11,55 +10,86 @@ import retrofit2.http.Path
import retrofit2.http.Query import retrofit2.http.Query
// codebeat:disable[ARITY]
internal interface SelfossService { internal interface SelfossService {
@GET("login") @GET("login")
fun loginToSelfoss(@Query("username") username: String, @Query("password") password: String): Call<SuccessResponse> fun loginToSelfoss(@Query("username") username: String, @Query("password") password: String): Call<SuccessResponse>
@GET("items") @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): Call<List<Item>>
@POST("mark/{id}") @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}") @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 @FormUrlEncoded
@POST("mark") @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}") @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}") @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") @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") @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") @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") @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") @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}") @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 @FormUrlEncoded
@POST("source") @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.LayoutRes;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.design.widget.AppBarLayout;
import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatDelegate; import android.support.v7.app.AppCompatDelegate;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.view.LayoutInflater;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.LinearLayout;
import apps.amine.bou.readerforselfoss.R;
import com.ftinc.scoop.Scoop;
/** /**
* A {@link PreferenceActivity} which implements and proxies the necessary calls * 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) { protected void onCreate(Bundle savedInstanceState) {
getDelegate().installViewFactory(); getDelegate().installViewFactory();
getDelegate().onCreate(savedInstanceState); getDelegate().onCreate(savedInstanceState);
Scoop.getInstance().apply(this);
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
} }
@Override @Override
protected void onPostCreate(Bundle savedInstanceState) { protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState); super.onPostCreate(savedInstanceState);
LinearLayout root = (LinearLayout)findViewById(android.R.id.list).getParent().getParent().getParent();
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); getDelegate().onPostCreate(savedInstanceState);
} }

View File

@ -20,6 +20,7 @@ import android.view.MenuItem;
import java.util.List; import java.util.List;
import apps.amine.bou.readerforselfoss.R; import apps.amine.bou.readerforselfoss.R;
import com.ftinc.scoop.ui.ScoopSettingsActivity;
/** /**
@ -162,18 +163,21 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
*/ */
@TargetApi(Build.VERSION_CODES.HONEYCOMB) @TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static class LinksPreferenceFragment extends PreferenceFragment { public static class LinksPreferenceFragment extends PreferenceFragment {
public void openUrl(Uri uri) {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(browserIntent);
}
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.pref_links); addPreferencesFromResource(R.xml.pref_links);
setHasOptionsMenu(true); setHasOptionsMenu(true);
Preference tracker = findPreference( "trackerLink" ); findPreference( "trackerLink" ).setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
tracker.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override @Override
public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.tracker_url))); openUrl(Uri.parse(getString(R.string.tracker_url)));
startActivity(browserIntent);
return true; return true;
} }
}); });
@ -181,8 +185,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
findPreference("sourceLink").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { findPreference("sourceLink").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override @Override
public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.source_url))); openUrl(Uri.parse(getString(R.string.source_url)));
startActivity(browserIntent);
return false; return false;
} }
}); });
@ -199,6 +202,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 @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId(); 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.support.v7.app.AlertDialog
import android.text.TextUtils import android.text.TextUtils
import android.util.Patterns import android.util.Patterns
import apps.amine.bou.readerforselfoss.BuildConfig
import apps.amine.bou.readerforselfoss.R
import com.google.firebase.remoteconfig.FirebaseRemoteConfig import com.google.firebase.remoteconfig.FirebaseRemoteConfig
import okhttp3.HttpUrl import okhttp3.HttpUrl
private fun isStoreVersion(context: Context): Boolean { import apps.amine.bou.readerforselfoss.BuildConfig
var result = false import apps.amine.bou.readerforselfoss.R
try { import apps.amine.bou.readerforselfoss.api.selfoss.Item
val installer = context.packageManager
.getInstallerPackageName(context.packageName)
result = !TextUtils.isEmpty(installer)
} catch (e: Throwable) {
}
return result
}
fun checkAndDisplayStoreApk(context: Context) = fun Context.checkAndDisplayStoreApk() = {
if (!isStoreVersion(context) && !BuildConfig.GITHUB_VERSION) { fun isStoreVersion(): Boolean =
val alertDialog = AlertDialog.Builder(context).create() try {
alertDialog.setTitle(context.getString(R.string.warning_version)) val installer = this.packageManager
alertDialog.setMessage(context.getString(R.string.text_version)) .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", alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "OK",
{ dialog, _ -> dialog.dismiss() }) { dialog, _ -> dialog.dismiss() })
alertDialog.show() alertDialog.show()
} else Unit } else Unit
}
fun String.isUrlValid(): Boolean =
HttpUrl.parse(this) != null && Patterns.WEB_URL.matcher(this).matches()
fun isUrlValid(url: String): Boolean { fun String.isBaseUrlValid(): Boolean {
val baseUrl = HttpUrl.parse(url) val baseUrl = HttpUrl.parse(this)
var existsAndEndsWithSlash = false var existsAndEndsWithSlash = false
if (baseUrl != null) { if (baseUrl != null) {
val pathSegments = baseUrl.pathSegments() val pathSegments = baseUrl.pathSegments()
existsAndEndsWithSlash = "" == pathSegments[pathSegments.size - 1] 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 = fun String?.isEmptyOrNullOrNullString(): Boolean =
str == null || str == "null" || str.isEmpty() this == null || this == "null" || this.isEmpty()
fun checkApkVersion(settings: SharedPreferences, editor: SharedPreferences.Editor, context: Context, mFirebaseRemoteConfig: FirebaseRemoteConfig) { fun Context.checkApkVersion(settings: SharedPreferences,
mFirebaseRemoteConfig.fetch(43200) editor: SharedPreferences.Editor,
.addOnCompleteListener { task -> mFirebaseRemoteConfig: FirebaseRemoteConfig) = {
if (task.isSuccessful) { fun isThereAnUpdate() {
mFirebaseRemoteConfig.activateFetched() val APK_LINK = "github_apk"
} else {
val apkLink = mFirebaseRemoteConfig.getString(APK_LINK)
val storedLink = settings.getString(APK_LINK, "")
if (apkLink != storedLink && !apkLink.isEmpty()) {
val alertDialog = AlertDialog.Builder(this).create()
alertDialog.setTitle(getString(R.string.new_apk_available_title))
alertDialog.setMessage(getString(R.string.new_apk_available_message))
alertDialog.setButton(AlertDialog.BUTTON_POSITIVE, getString(R.string.new_apk_available_get)) { _, _ ->
editor.putString(APK_LINK, apkLink)
editor.apply()
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(apkLink))
startActivity(browserIntent)
} }
alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, getString(R.string.new_apk_available_no),
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),
{ dialog, _ -> { dialog, _ ->
editor.putString(APK_LINK, apkLink) editor.putString(APK_LINK, apkLink)
editor.apply() editor.apply()
dialog.dismiss() 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 package apps.amine.bou.readerforselfoss.utils
import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import apps.amine.bou.readerforselfoss.LoginActivity
class Config(c: Context) { class Config(c: Context) {
private val settings: SharedPreferences val settings: SharedPreferences = c.getSharedPreferences(settingsName, Context.MODE_PRIVATE)
init {
this.settings = c.getSharedPreferences(settingsName, Context.MODE_PRIVATE)
}
val baseUrl: String val baseUrl: String
get() = settings.getString("url", "") get() = settings.getString("url", "")
@ -27,8 +26,24 @@ class Config(c: Context) {
val httpUserPassword: String val httpUserPassword: String
get() = settings.getString("httpPassword", "") get() = settings.getString("httpPassword", "")
companion object { companion object {
val settingsName = "paramsselfoss" 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,24 @@
package apps.amine.bou.readerforselfoss.utils
import android.content.Context
import android.graphics.Bitmap
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory
import android.widget.ImageView
import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.BitmapImageViewTarget
fun Context.bitmapCenterCrop(url: String, iv: ImageView) =
Glide.with(this).load(url).asBitmap().centerCrop().into(iv)
fun Context.bitmapFitCenter(url: String, iv: ImageView) =
Glide.with(this).load(url).asBitmap().fitCenter().into(iv)
fun Context.circularBitmapDrawable(url: String, iv: ImageView) =
Glide.with(this).load(url).asBitmap().centerCrop().into(object : BitmapImageViewTarget(iv) {
override fun setResource(resource: Bitmap) {
val circularBitmapDrawable = RoundedBitmapDrawableFactory.create(resources, resource)
circularBitmapDrawable.isCircular = true
iv.setImageDrawable(circularBitmapDrawable)
}
})

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.graphics.BitmapFactory
import android.net.Uri import android.net.Uri
import android.support.customtabs.CustomTabsIntent import android.support.customtabs.CustomTabsIntent
import xyz.klinker.android.drag_dismiss.DragDismissIntentBuilder
import apps.amine.bou.readerforselfoss.R import apps.amine.bou.readerforselfoss.R
import apps.amine.bou.readerforselfoss.ReaderActivity import apps.amine.bou.readerforselfoss.ReaderActivity
import apps.amine.bou.readerforselfoss.api.selfoss.Item import apps.amine.bou.readerforselfoss.api.selfoss.Item
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper 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) fun Context.buildCustomTabsIntent(): CustomTabsIntent {
actionIntent.type = "text/plain"
return PendingIntent.getActivity( val actionIntent = Intent(Intent.ACTION_SEND)
c, 0, actionIntent, 0) actionIntent.type = "text/plain"
} val createPendingShareIntent: PendingIntent = PendingIntent.getActivity(this, 0, actionIntent, 0)
val intentBuilder = CustomTabsIntent.Builder() val intentBuilder = CustomTabsIntent.Builder()
// TODO: change to primary when it's possible to customize custom tabs title color // 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.getResources().getColor(R.color.colorPrimary));
intentBuilder.setToolbarColor(c.resources.getColor(R.color.colorAccentDark)) intentBuilder.setToolbarColor(resources.getColor(R.color.colorAccentDark))
intentBuilder.setShowTitle(true) intentBuilder.setShowTitle(true)
intentBuilder.setStartAnimations(c, intentBuilder.setStartAnimations(this,
R.anim.slide_in_right, R.anim.slide_in_right,
R.anim.slide_out_left) R.anim.slide_out_left)
intentBuilder.setExitAnimations(c, intentBuilder.setExitAnimations(this,
android.R.anim.slide_in_left, android.R.anim.slide_in_left,
android.R.anim.slide_out_right) android.R.anim.slide_out_right)
val closeicon = BitmapFactory.decodeResource(c.resources, R.drawable.ic_close_white_24dp) val closeicon = BitmapFactory.decodeResource(resources, R.drawable.ic_close_white_24dp)
intentBuilder.setCloseButtonIcon(closeicon) intentBuilder.setCloseButtonIcon(closeicon)
val shareLabel = c.getString(R.string.label_share) val shareLabel = this.getString(R.string.label_share)
val icon = BitmapFactory.decodeResource(c.resources, val icon = BitmapFactory.decodeResource(resources,
R.drawable.ic_share_white_24dp) R.drawable.ic_share_white_24dp)
intentBuilder.setActionButton(icon, shareLabel, createPendingShareIntent(c)) intentBuilder.setActionButton(icon, shareLabel, createPendingShareIntent)
return intentBuilder.build() return intentBuilder.build()
} }
fun openItemUrl(i: Item, fun Context.openItemUrl(linkDecoded: String,
customTabsIntent: CustomTabsIntent, customTabsIntent: CustomTabsIntent,
internalBrowser: Boolean, internalBrowser: Boolean,
articleViewer: Boolean, articleViewer: Boolean,
app: Activity, app: Activity) {
c: Context) { if (!internalBrowser || !linkDecoded.isUrlValid()) {
if (!internalBrowser) { openInBrowser(linkDecoded, app)
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse(i.getLinkDecoded())
app.startActivity(intent)
} else { } else {
if (articleViewer) { 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 .setFullscreenOnTablets(true) // defaults to false, tablets will have padding on each side
.setDragElasticity(DragDismissIntentBuilder.DragElasticity.NORMAL) // Larger elasticities will make it easier to dismiss. .setDragElasticity(DragDismissIntentBuilder.DragElasticity.NORMAL) // Larger elasticities will make it easier to dismiss.
.build(intent) .build(intent)
intent.putExtra("url", i.getLinkDecoded()) intent.putExtra("url", linkDecoded)
app.startActivity(intent) app.startActivity(intent)
} else { } else {
CustomTabActivityHelper.openCustomTab(app, customTabsIntent, Uri.parse(i.getLinkDecoded()) try {
) { _, uri -> CustomTabActivityHelper.openCustomTab(app, customTabsIntent, Uri.parse(linkDecoded)
val intent = Intent(Intent.ACTION_VIEW, uri) ) { _, uri ->
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK val intent = Intent(Intent.ACTION_VIEW, uri)
c.startActivity(intent) 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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 680 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.

After

Width:  |  Height:  |  Size: 498 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 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.

After

Width:  |  Height:  |  Size: 339 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 634 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.

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: 741 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 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: 966 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 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"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout <LinearLayout
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin" tools:context="apps.amine.bou.readerforselfoss.AddSourceActivity"
android:paddingLeft="@dimen/activity_horizontal_margin" android:orientation="vertical">
android:paddingRight="@dimen/activity_horizontal_margin" <android.support.design.widget.AppBarLayout
android:paddingTop="@dimen/activity_vertical_margin" android:layout_width="match_parent"
tools:context="apps.amine.bou.readerforselfoss.AddSourceActivity"> 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.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" android:layout_height="match_parent"
app:layout_constraintTop_toTopOf="parent" android:layout_width="match_parent">
app:layout_constraintRight_toRightOf="parent" <android.support.constraint.ConstraintLayout
app:layout_constraintLeft_toLeftOf="parent" android:layout_width="match_parent"
app:layout_constraintBottom_toBottomOf="parent" android:layout_height="match_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"
app:layout_constraintTop_toTopOf="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_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
android:layout_marginTop="16dp" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/sourceUri" android:id="@+id/formContainer"
android:hint="@string/add_source_hint_tags" android:visibility="gone"
android:inputType="text"/> app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintVertical_bias="0.0">
<Spinner <TextView
android:layout_width="match_parent" android:text="@string/add_source"
android:id="@+id/spoutsSpinner" android:layout_width="wrap_content"
android:layout_marginTop="16dp" android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/tags" android:id="@+id/textView2"
app:layout_constraintRight_toRightOf="parent" android:textAppearance="@style/TextAppearance.AppCompat.Large"
app:layout_constraintLeft_toLeftOf="parent" android:textAlignment="center"
android:layout_height="40dp"/> 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 <EditText
android:text="@string/add_source_save" 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_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:id="@+id/saveBtn" android:id="@+id/progress"
android:elevation="5dp" app:layout_constraintTop_toTopOf="parent"
android:textColor="@color/colorAccent"
android:layout_marginEnd="16dp"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginRight="16dp"
android:layout_marginStart="16dp"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
android:layout_marginLeft="16dp" app:layout_constraintRight_toRightOf="parent"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@+id/spoutsSpinner"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="16dp" android:visibility="visible"/>
app:layout_constraintVertical_bias="0.0"/>
</android.support.constraint.ConstraintLayout> </android.support.constraint.ConstraintLayout>
<ProgressBar </LinearLayout>
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>

View File

@ -5,6 +5,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context="apps.amine.bou.readerforselfoss.HomeActivity" tools:context="apps.amine.bou.readerforselfoss.HomeActivity"
android:fitsSystemWindows="true"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<com.github.stkent.amplify.prompt.DefaultLayoutPromptView <com.github.stkent.amplify.prompt.DefaultLayoutPromptView
@ -26,6 +27,7 @@
app:prompt_view_positive_button_border_color="@color/colorPrimary" app:prompt_view_positive_button_border_color="@color/colorPrimary"
app:prompt_view_negative_button_background_color="@color/colorAccent" app:prompt_view_negative_button_background_color="@color/colorAccent"
app:prompt_view_negative_button_border_color="@color/white" app:prompt_view_negative_button_border_color="@color/white"
app:prompt_view_background_color="?attr/colorAccent"
app:prompt_view_thanks_display_time_ms="2000"/> app:prompt_view_thanks_display_time_ms="2000"/>
<android.support.design.widget.CoordinatorLayout <android.support.design.widget.CoordinatorLayout
@ -34,34 +36,77 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_below="@id/prompt_view"> android:layout_below="@id/prompt_view">
<android.support.design.widget.CoordinatorLayout
<android.support.v4.widget.SwipeRefreshLayout android:id="@+id/intern_coordLayout"
android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView <LinearLayout
android:id="@+id/my_recycler_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:background="@color/background_grey" android:orientation="vertical">
android:clipToPadding="false"
android:scrollbars="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</android.support.v4.widget.SwipeRefreshLayout>
<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:id="@+id/bottomBar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="60dp" 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.support.design.widget.CoordinatorLayout> </android.support.design.widget.CoordinatorLayout>
</RelativeLayout> </RelativeLayout>

View File

@ -2,136 +2,154 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:orientation="vertical" 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"> tools:context="apps.amine.bou.readerforselfoss.LoginActivity">
<android.support.design.widget.AppBarLayout
<!-- 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:layout_width="match_parent" 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_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: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_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
> android:orientation="vertical">
<EditText <android.support.design.widget.TextInputLayout
android:id="@+id/url"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/prompt_url" android:id="@+id/urlLayout"
android:imeActionId="@+id/login" >
android:inputType="textUri"
android:imeOptions="actionUnspecified"
android:maxLines="1"
/>
</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.support.design.widget.TextInputLayout>
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 <Switch
android:id="@+id/loginLayout" android:text="@string/withLoginSwitch"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="0dp"
android:visibility="gone"> android:id="@+id/withLogin"
android:layout_weight="1"/>
<AutoCompleteTextView <android.support.design.widget.TextInputLayout
android:id="@+id/login" android:id="@+id/loginLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/prompt_login" android:visibility="gone">
android:inputType="text"
android:maxLines="1"
/>
</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.support.design.widget.TextInputLayout>
android:id="@+id/passwordLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone">
<EditText <android.support.design.widget.TextInputLayout
android:id="@+id/password" android:id="@+id/passwordLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/prompt_password" android:visibility="gone">
android:inputType="textPassword"
android:maxLines="1"
/>
</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.support.design.widget.TextInputLayout>
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 <Switch
android:id="@+id/httpLoginInput" android:id="@+id/withHttpLogin"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone">
<EditText
android:id="@+id/httpLogin"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/prompt_http_login" /> android:layout_weight="1"
</android.support.design.widget.TextInputLayout> android:text="@string/withHttpLoginSwitch" />
<android.support.design.widget.TextInputLayout <android.support.design.widget.TextInputLayout
android:id="@+id/httpPasswordInput" android:id="@+id/httpLoginInput"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:visibility="gone"> android:visibility="gone">
<EditText <EditText
android:id="@+id/httpPassword" 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>
<Button
android:id="@+id/email_sign_in_button"
style="?android:textAppearanceSmall"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:inputType="textPassword" android:layout_marginBottom="16dp"
android:hint="@string/prompt_http_password" /> android:layout_marginTop="16dp"
</android.support.design.widget.TextInputLayout> android:text="@string/action_sign_in"
android:textStyle="bold" />
<Button </LinearLayout>
android:id="@+id/email_sign_in_button" </ScrollView>
style="?android:textAppearanceSmall" </LinearLayout>
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>

View File

@ -5,7 +5,8 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context="apps.amine.bou.readerforselfoss.ReaderActivity"> tools:context="apps.amine.bou.readerforselfoss.ReaderActivity"
android:background="?android:attr/windowBackground">
<ImageView <ImageView
android:id="@+id/imageView" android:id="@+id/imageView"
@ -96,7 +97,7 @@
android:padding="4dp" android:padding="4dp"
android:scaleType="centerCrop" android:scaleType="centerCrop"
android:src="@drawable/ic_open_in_browser_black_24dp" android:src="@drawable/ic_open_in_browser_black_24dp"
android:tint="#000000" android:tint="?android:attr/textColorPrimary"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/shareBtn" app:layout_constraintEnd_toStartOf="@+id/shareBtn"
app:layout_constraintHorizontal_bias="0.5" app:layout_constraintHorizontal_bias="0.5"
@ -113,7 +114,7 @@
android:padding="4dp" android:padding="4dp"
android:scaleType="centerCrop" android:scaleType="centerCrop"
android:src="@drawable/ic_share_black_24dp" android:src="@drawable/ic_share_black_24dp"
android:tint="#000000" android:tint="?android:attr/textColorPrimary"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5" app:layout_constraintHorizontal_bias="0.5"

View File

@ -8,12 +8,22 @@
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context="apps.amine.bou.readerforselfoss.SourcesActivity" tools:context="apps.amine.bou.readerforselfoss.SourcesActivity"
xmlns:fab="http://schemas.android.com/apk/res-auto"> 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.support.v7.widget.RecyclerView
android:id="@+id/activity_sources" android:id="@+id/activity_sources"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/background_grey"
android:scrollbars="vertical" android:scrollbars="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior"> app:layout_behavior="@string/appbar_scrolling_view_behavior">
</android.support.v7.widget.RecyclerView> </android.support.v7.widget.RecyclerView>
@ -24,8 +34,9 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="end|bottom|right" android:layout_gravity="end|bottom|right"
android:src="@drawable/ic_add_black_24dp" android:src="@drawable/ic_add_black_24dp"
fab:fab_colorNormal="@color/colorAccent" android:tint="?android:textColorPrimary"
fab:fab_colorPressed="@color/colorAccentDark" fab:fab_colorNormal="?attr/colorAccent"
fab:fab_colorPressed="?attr/colorAccentDark"
fab:fab_colorRipple="@color/pink" fab:fab_colorRipple="@color/pink"
android:paddingBottom="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingTop="@dimen/activity_vertical_margin" android:paddingTop="@dimen/activity_vertical_margin"

View File

@ -6,13 +6,15 @@
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<android.support.v7.widget.CardView <android.support.v7.widget.CardView
android:id="@+id/card"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="0dp" android:layout_width="0dp"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintHorizontal_bias="0.62" app:layout_constraintHorizontal_bias="0.62"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:cardElevation="2dp" card_view:cardElevation="2dp"
app:cardUseCompatPadding="true" card_view:cardUseCompatPadding="true"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginLeft="8dp" android:layout_marginLeft="8dp"
@ -60,24 +62,21 @@
android:layout_width="40dp" /> android:layout_width="40dp" />
<TextView <TextView
android:id="@+id/title"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" 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" android:layout_marginEnd="8dp"
app:layout_constraintRight_toRightOf="parent" android:layout_marginLeft="8dp"
android:layout_marginRight="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_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toRightOf="@+id/sourceImage" app:layout_constraintLeft_toRightOf="@+id/sourceImage"
android:layout_marginStart="8dp" app:layout_constraintRight_toRightOf="parent"
android:layout_marginLeft="8dp"
app:layout_constraintTop_toTopOf="@+id/sourceImage" app:layout_constraintTop_toTopOf="@+id/sourceImage"
android:gravity="start" /> tools:text="Titre" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -85,7 +84,6 @@
android:id="@+id/sourceTitleAndDate" android:id="@+id/sourceTitleAndDate"
android:textSize="14sp" android:textSize="14sp"
tools:text="Google Actualité Il y a 5h" tools:text="Google Actualité Il y a 5h"
android:textColor="#868686"
android:textAlignment="viewStart" android:textAlignment="viewStart"
app:layout_constraintTop_toBottomOf="@+id/title" app:layout_constraintTop_toBottomOf="@+id/title"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
@ -117,38 +115,38 @@
android:layout_marginEnd="8dp"/> android:layout_marginEnd="8dp"/>
<ImageButton <ImageButton
android:id="@+id/shareBtn"
android:layout_width="35dp" android:layout_width="35dp"
android:layout_height="35dp" android:layout_height="35dp"
android:src="@drawable/ic_share_black_24dp"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_toLeftOf="@+id/favButton" android:layout_toLeftOf="@+id/favButton"
android:layout_toStartOf="@+id/favButton" android:layout_toStartOf="@+id/favButton"
android:id="@+id/shareBtn" android:adjustViewBounds="true"
android:background="@android:color/transparent" android:background="@android:color/transparent"
android:elevation="5dp" android:elevation="5dp"
android:layout_marginEnd="16dp" android:padding="4dp"
android:layout_marginRight="16dp"
android:tint="#000000"
android:adjustViewBounds="true"
android:scaleType="centerCrop" android:scaleType="centerCrop"
android:padding="4dp"/> android:src="@drawable/ic_share_black_24dp"
android:tint="?android:attr/textColorPrimary" />
<ImageButton <ImageButton
android:id="@+id/browserBtn"
android:layout_width="35dp" android:layout_width="35dp"
android:layout_height="35dp" android:layout_height="35dp"
android:src="@drawable/ic_open_in_browser_black_24dp"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/shareBtn"
android:layout_toStartOf="@+id/shareBtn"
android:id="@+id/browserBtn"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:layout_marginRight="16dp" android:layout_marginRight="16dp"
android:elevation="5dp" android:layout_toLeftOf="@+id/shareBtn"
android:background="@android:color/transparent" android:layout_toStartOf="@+id/shareBtn"
android:tint="#000000"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:background="@android:color/transparent"
android:elevation="5dp"
android:padding="4dp"
android:scaleType="centerCrop" android:scaleType="centerCrop"
android:padding="4dp"/> android:src="@drawable/ic_open_in_browser_black_24dp"
android:tint="?android:attr/textColorPrimary" />
</RelativeLayout> </RelativeLayout>

View File

@ -3,11 +3,10 @@
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal" android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:minHeight="88dp" android:minHeight="88dp">
android:background="#EDEDED">
<ImageView <ImageView
android:id="@+id/itemImage" android:id="@+id/itemImage"
@ -20,25 +19,25 @@
/> />
<TextView <TextView
android:id="@+id/title"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" 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" android:layout_marginEnd="16dp"
app:layout_constraintRight_toRightOf="parent" android:layout_marginLeft="16dp"
android:layout_marginRight="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" 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 <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -46,7 +45,6 @@
android:id="@+id/sourceTitleAndDate" android:id="@+id/sourceTitleAndDate"
android:textSize="14sp" android:textSize="14sp"
tools:text="Google Actualité Il y a 5h" tools:text="Google Actualité Il y a 5h"
android:textColor="#868686"
android:textAlignment="viewStart" android:textAlignment="viewStart"
app:layout_constraintTop_toBottomOf="@+id/title" app:layout_constraintTop_toBottomOf="@+id/title"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
@ -90,11 +88,11 @@
android:layout_toStartOf="@+id/favButton" android:layout_toStartOf="@+id/favButton"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:background="@android:color/transparent" android:background="@android:color/transparent"
android:backgroundTint="?android:attr/textColorPrimary"
android:elevation="5dp" android:elevation="5dp"
android:padding="4dp" android:padding="4dp"
android:scaleType="centerCrop" android:scaleType="centerCrop"
android:src="@drawable/ic_share_black_24dp" android:src="@drawable/ic_share_black_24dp" />
android:tint="#000000" />
<ImageButton <ImageButton
android:id="@+id/browserBtn" android:id="@+id/browserBtn"
@ -107,11 +105,11 @@
android:layout_toStartOf="@+id/shareBtn" android:layout_toStartOf="@+id/shareBtn"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:background="@android:color/transparent" android:background="@android:color/transparent"
android:backgroundTint="?android:attr/textColorPrimary"
android:elevation="5dp" android:elevation="5dp"
android:padding="4dp" android:padding="4dp"
android:scaleType="centerCrop" android:scaleType="centerCrop"
android:src="@drawable/ic_open_in_browser_black_24dp" android:src="@drawable/ic_open_in_browser_black_24dp" />
android:tint="#000000" />
</RelativeLayout> </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" xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical" android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:background="#EDEDED"
android:layout_height="48dp"> android:layout_height="48dp">
@ -37,6 +36,7 @@
android:id="@+id/deleteBtn" android:id="@+id/deleteBtn"
android:background="@drawable/ic_remove_circle_outline_black_24dp" android:background="@drawable/ic_remove_circle_outline_black_24dp"
style="@style/Widget.AppCompat.Button.Borderless" style="@style/Widget.AppCompat.Button.Borderless"
android:backgroundTint="?android:textColorSecondary"
android:elevation="4dp" android:elevation="4dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"

View File

@ -2,36 +2,28 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> 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" <item android:id="@+id/readAll"
android:icon="@drawable/ic_done_all_white_24dp" android:icon="@drawable/ic_done_all_white_24dp"
android:title="@string/readAll" android:title="@string/readAll"
android:orderInCategory="1" android:orderInCategory="1"
app:showAsAction="ifRoom"/> app:showAsAction="always"/>
<item <item
android:id="@+id/refresh" android:id="@+id/refresh"
android:icon="@drawable/ic_refresh" android:icon="@drawable/ic_refresh"
android:orderInCategory="99" android:orderInCategory="99"
android:title="@string/menu_home_refresh" 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"/>
<item <item
android:id="@+id/action_share_the_app" android:id="@+id/action_share_the_app"
android:orderInCategory="102" android:orderInCategory="102"
android:title="@string/menu_share_the_app" /> 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" <item android:id="@+id/action_disconnect"
android:title="@string/action_disconnect" android:title="@string/action_disconnect"

View File

@ -15,7 +15,6 @@
<string name="label_share">"Partager"</string> <string name="label_share">"Partager"</string>
<string name="readAll">"Tout lire"</string> <string name="readAll">"Tout lire"</string>
<string name="action_disconnect">"Déconnecter"</string> <string name="action_disconnect">"Déconnecter"</string>
<string name="action_source">"Sources"</string>
<string name="title_activity_settings">"Paramètres"</string> <string name="title_activity_settings">"Paramètres"</string>
<string name="pref_header_general">"General"</string> <string name="pref_header_general">"General"</string>
<string name="pref_switch_actions_tap_title">"Action du clique sur un article"</string> <string name="pref_switch_actions_tap_title">"Action du clique sur un article"</string>
@ -103,4 +102,33 @@
<string name="card_height_off" >La taille de la carte sera fixe</string> <string name="card_height_off" >La taille de la carte sera fixe</string>
<string name="source_code">Code source</string> <string name="source_code">Code source</string>
<string name="cant_mark_read">Kan het artikel niet als gelezen markeren</string> <string name="cant_mark_read">Kan het artikel niet als gelezen markeren</string>
<string name="drawer_error_loading_tags">Erreur lors du chargement des tags…</string>
<string name="drawer_error_loading_sources">Erreur lors du chargement des sources…</string>
<string name="drawer_item_filters">Filtres</string>
<string name="drawer_action_clear">raz</string>
<string name="drawer_item_tags">Tags</string>
<string name="drawer_item_sources">Sources</string>
<string name="drawer_action_edit">editer</string>
<string name="cache_drawer_error">Impossible de mettre en cache les filtres pour le drawer</string>
<string name="no_tags_loaded">Pas de tags chargés</string>
<string name="no_sources_loaded">Pas de sources chargés</string>
<string name="drawer_loading">Chargement …</string>
<string name="menu_home_search">Rechercher</string>
<string name="can_delete_source">Petit soucis lors de la suppression de la source.</string>
<string name="base_url_error">Il y a eu un souci lors de la communication avec votre instance Selfoss. Si le problèmes persiste, contactez-moi pour trouver une solution.</string>
<string name="pref_header_theme">Thèmes</string>
<string name="default_theme">Par défaut</string>
<string name="teal_orange_theme">Sarcelle/Orange/Clair</string>
<string name="cyan_pink_theme">Cyan/Rose/Clair</string>
<string name="grey_orange_theme">Gris/Orange/Clair</string>
<string name="blue_amber_theme">Bleu/Ambre/Clair</string>
<string name="indigo_pink_theme">Indigo/Rose/Clair</string>
<string name="red_teal_theme">Rouge/Sarcelle/Clair</string>
<string name="teal_orange_dark_theme">Sarcelle/Orange/Foncé</string>
<string name="cyan_pink_dark_theme">Cyan/Rose/Foncé</string>
<string name="default_dark_theme">Par défaut/Foncé</string>
<string name="grey_orange_dark_theme">Gris/Orange/Foncé</string>
<string name="blue_amber_dark_theme">Bleu/Ambre/Foncé</string>
<string name="indigo_pink_dark_theme">Indigo/Rose/Foncé</string>
<string name="red_teal_dark_theme">Rouge/Sarcelle/Foncé</string>
</resources> </resources>

View File

@ -15,7 +15,6 @@
<string name="label_share">"Delen"</string> <string name="label_share">"Delen"</string>
<string name="readAll">"Alles lezen"</string> <string name="readAll">"Alles lezen"</string>
<string name="action_disconnect">"Verbinding verbreken"</string> <string name="action_disconnect">"Verbinding verbreken"</string>
<string name="action_source">"Bronnen"</string>
<string name="title_activity_settings">"Instellingen"</string> <string name="title_activity_settings">"Instellingen"</string>
<string name="pref_header_general">"Algemeen"</string> <string name="pref_header_general">"Algemeen"</string>
<string name="pref_switch_actions_tap_title">"Actie bij tikken op artikelen"</string> <string name="pref_switch_actions_tap_title">"Actie bij tikken op artikelen"</string>
@ -103,4 +102,33 @@
<string name="card_height_off" >Vaste hoogte</string> <string name="card_height_off" >Vaste hoogte</string>
<string name="source_code">Source code</string> <string name="source_code">Source code</string>
<string name="cant_mark_read">Impossible de marquer l\'article comme lu</string> <string name="cant_mark_read">Impossible de marquer l\'article comme lu</string>
<string name="drawer_error_loading_tags">Error loading tags…</string>
<string name="drawer_error_loading_sources">Error loading sources…</string>
<string name="drawer_item_filters">Filters</string>
<string name="drawer_action_clear">clear</string>
<string name="drawer_item_tags">Tags</string>
<string name="drawer_item_sources">Sources</string>
<string name="drawer_action_edit">edit</string>
<string name="cache_drawer_error">Couldn\'t cache your drawer data</string>
<string name="no_tags_loaded">No tags loaded</string>
<string name="no_sources_loaded">No sources loaded</string>
<string name="drawer_loading">Loading …</string>
<string name="menu_home_search">Zoeken</string>
<string name="can_delete_source">Can\'t delete the source...</string>
<string name="base_url_error">There was an issue when trying to communicate with your Selfoss Instance. If the issue persists, please get in touch with me.</string>
<string name="pref_header_theme">Themes</string>
<string name="default_theme">Default</string>
<string name="teal_orange_theme">Teal/Orange/Light</string>
<string name="cyan_pink_theme">Cyan/Pink/Light</string>
<string name="grey_orange_theme">Grey/Orange/Light</string>
<string name="blue_amber_theme">Blue/Amber/Light</string>
<string name="indigo_pink_theme">Indigo/Pink/Light</string>
<string name="red_teal_theme">Red/Teal/Light</string>
<string name="teal_orange_dark_theme">Teal/Orange/Dark</string>
<string name="cyan_pink_dark_theme">Cyan/Pink/Dark</string>
<string name="default_dark_theme">Default/Dark</string>
<string name="grey_orange_dark_theme">Grey/Orange/Dark</string>
<string name="blue_amber_dark_theme">Blue/Amber/Dark</string>
<string name="indigo_pink_dark_theme">Indigo/Pink/Dark</string>
<string name="red_teal_dark_theme">Red/Teal/Dark</string>
</resources> </resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="Theme">
<attr name="colorAccentDark" format="reference|color" />
</declare-styleable>
</resources>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="material_drawer_item_custom_url_item" type="id" />
</resources>

View File

@ -1,4 +1,4 @@
<resources> <resources xmlns:tools="http://schemas.android.com/tools">
<string name="app_name">"Reader for Selfoss"</string> <string name="app_name">"Reader for Selfoss"</string>
<string name="title_activity_login">"Log in"</string> <string name="title_activity_login">"Log in"</string>
<string name="prompt_password">"Password"</string> <string name="prompt_password">"Password"</string>
@ -15,7 +15,6 @@
<string name="label_share">"Share"</string> <string name="label_share">"Share"</string>
<string name="readAll">"Read all"</string> <string name="readAll">"Read all"</string>
<string name="action_disconnect">"Disconnect"</string> <string name="action_disconnect">"Disconnect"</string>
<string name="action_source">"Sources"</string>
<string name="title_activity_settings">"Settings"</string> <string name="title_activity_settings">"Settings"</string>
<string name="pref_header_general">"General"</string> <string name="pref_header_general">"General"</string>
<string name="pref_switch_actions_tap_title">"Tap action on the articles"</string> <string name="pref_switch_actions_tap_title">"Tap action on the articles"</string>
@ -105,4 +104,33 @@
<string name="card_height_off">Card height will be fixed</string> <string name="card_height_off">Card height will be fixed</string>
<string name="source_code">Source code</string> <string name="source_code">Source code</string>
<string name="cant_mark_read">Can\'t mark article as read</string> <string name="cant_mark_read">Can\'t mark article as read</string>
<string name="drawer_error_loading_tags">Error loading tags…</string>
<string name="drawer_error_loading_sources">Error loading sources…</string>
<string name="drawer_item_filters">Filters</string>
<string name="drawer_action_clear">clear</string>
<string name="drawer_item_tags">Tags</string>
<string name="drawer_item_sources">Sources</string>
<string name="drawer_action_edit">edit</string>
<string name="cache_drawer_error" tools:keep="@string/cache_drawer_error">Couldn\'t cache your drawer data</string>
<string name="no_tags_loaded">No tags loaded</string>
<string name="no_sources_loaded">No sources loaded</string>
<string name="drawer_loading">Loading …</string>
<string name="menu_home_search">Search</string>
<string name="can_delete_source">Can\'t delete the source...</string>
<string name="base_url_error">There was an issue when trying to communicate with your Selfoss Instance. If the issue persists, please get in touch with me.</string>
<string name="pref_header_theme">Themes</string>
<string name="default_theme">Default</string>
<string name="teal_orange_theme">Teal/Orange/Light</string>
<string name="cyan_pink_theme">Cyan/Pink/Light</string>
<string name="grey_orange_theme">Grey/Orange/Light</string>
<string name="blue_amber_theme">Blue/Amber/Light</string>
<string name="indigo_pink_theme">Indigo/Pink/Light</string>
<string name="red_teal_theme">Red/Teal/Light</string>
<string name="teal_orange_dark_theme">Teal/Orange/Dark</string>
<string name="cyan_pink_dark_theme">Cyan/Pink/Dark</string>
<string name="default_dark_theme">Default/Dark</string>
<string name="grey_orange_dark_theme">Grey/Orange/Dark</string>
<string name="blue_amber_dark_theme">Blue/Amber/Dark</string>
<string name="indigo_pink_dark_theme">Indigo/Pink/Dark</string>
<string name="red_teal_dark_theme">Red/Teal/Dark</string>
</resources> </resources>

View File

@ -1,16 +1,179 @@
<resources> <resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:textColor">#000000</item>
</style>
<style name="SplashTheme" parent="Theme.AppCompat.NoActionBar"> <style name="SplashTheme" parent="Theme.AppCompat.NoActionBar">
<item name="android:windowBackground">@drawable/background_splash</item> <item name="android:windowBackground">@drawable/background_splash</item>
</style> </style>
<style name="NoBar" parent="MaterialDrawerTheme.Light">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="colorAccentDark">@color/colorAccentDark</item>
<item name="cardBackgroundColor">@color/white</item>
<item name="android:colorBackground">@color/md_grey_50</item>
<item name="android:textColorPrimary">@color/md_grey_900</item>
<item name="android:textColorSecondary">@color/md_grey_400</item>
</style>
<style name="NoBarDark" parent="MaterialDrawerTheme">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="colorAccentDark">@color/colorAccentDark</item>
<item name="cardBackgroundColor">@color/md_grey_800</item>
<item name="android:colorBackground">#303030</item>
<item name="bnbBackgroundColor">@color/md_grey_900</item>
<item name="android:textColorPrimary">@color/md_white_1000</item>
<item name="android:textColorSecondary">@color/md_grey_600</item>
</style>
<!-- ToolBar -->
<style name="ToolBarStyle" parent="Theme.AppCompat">
<item name="android:textColorPrimary">@color/white</item>
<item name="android:textColorSecondary">@color/white</item>
<item name="actionMenuTextColor">@color/white</item>
<!--<item name="actionOverflowButtonStyle">@style/ActionButtonOverflowStyle</item>
<item name="drawerArrowStyle">@style/DrawerArrowStyle</item>-->
</style>
<style name="NoBarBlueAmber" parent="MaterialDrawerTheme.Light">
<item name="colorPrimary">@color/md_blue_500</item>
<item name="colorPrimaryDark">@color/md_blue_700</item>
<item name="colorAccent">@color/md_amber_500</item>
<item name="colorAccentDark">@color/md_amber_700</item>
<item name="cardBackgroundColor">@color/white</item>
<item name="android:colorBackground">@color/md_grey_50</item>
<item name="android:textColorPrimary">@color/md_grey_900</item>
<item name="android:textColorSecondary">@color/md_grey_400</item>
</style>
<style name="NoBarBlueAmberDark" parent="MaterialDrawerTheme">
<item name="colorPrimary">@color/md_blue_500</item>
<item name="colorPrimaryDark">@color/md_blue_700</item>
<item name="colorAccent">@color/md_amber_500</item>
<item name="colorAccentDark">@color/md_amber_700</item>
<item name="cardBackgroundColor">@color/md_grey_800</item>
<item name="android:colorBackground">#303030</item>
<item name="bnbBackgroundColor">@color/md_grey_900</item>
<item name="android:textColorPrimary">@color/md_white_1000</item>
<item name="android:textColorSecondary">@color/md_grey_600</item>
</style>
<style name="NoBarGreyOrange" parent="MaterialDrawerTheme.Light">
<item name="colorPrimary">@color/md_blue_grey_500</item>
<item name="colorPrimaryDark">@color/md_blue_grey_700</item>
<item name="colorAccent">@color/md_deep_orange_500</item>
<item name="colorAccentDark">@color/md_deep_orange_700</item>
<item name="cardBackgroundColor">@color/white</item>
<item name="android:colorBackground">@color/md_grey_50</item>
<item name="android:textColorPrimary">@color/md_grey_900</item>
<item name="android:textColorSecondary">@color/md_grey_400</item>
</style>
<style name="NoBarGreyOrangeDark" parent="MaterialDrawerTheme">
<item name="colorPrimary">@color/md_blue_grey_500</item>
<item name="colorPrimaryDark">@color/md_blue_grey_700</item>
<item name="colorAccent">@color/md_deep_orange_500</item>
<item name="colorAccentDark">@color/md_deep_orange_700</item>
<item name="cardBackgroundColor">@color/md_grey_800</item>
<item name="android:colorBackground">#303030</item>
<item name="bnbBackgroundColor">@color/md_grey_900</item>
<item name="android:textColorPrimary">@color/md_white_1000</item>
<item name="android:textColorSecondary">@color/md_grey_600</item>
</style>
<style name="NoBarIndigoPink" parent="MaterialDrawerTheme.Light">
<item name="colorPrimary">@color/md_indigo_500</item>
<item name="colorPrimaryDark">@color/md_indigo_700</item>
<item name="colorAccent">@color/md_pink_500</item>
<item name="colorAccentDark">@color/md_pink_700</item>
<item name="cardBackgroundColor">@color/white</item>
<item name="android:colorBackground">@color/md_grey_50</item>
<item name="android:textColorPrimary">@color/md_grey_900</item>
<item name="android:textColorSecondary">@color/md_grey_400</item>
</style>
<style name="NoBarIndigoPinkDark" parent="MaterialDrawerTheme">
<item name="colorPrimary">@color/md_indigo_500</item>
<item name="colorPrimaryDark">@color/md_indigo_700</item>
<item name="colorAccent">@color/md_pink_500</item>
<item name="colorAccentDark">@color/md_pink_700</item>
<item name="cardBackgroundColor">@color/md_grey_800</item>
<item name="android:colorBackground">#303030</item>
<item name="bnbBackgroundColor">@color/md_grey_900</item>
<item name="android:textColorPrimary">@color/md_white_1000</item>
<item name="android:textColorSecondary">@color/md_grey_600</item>
</style>
<style name="NoBarRedTeal" parent="MaterialDrawerTheme.Light">
<item name="colorPrimary">@color/md_red_500</item>
<item name="colorPrimaryDark">@color/md_red_700</item>
<item name="colorAccent">@color/md_teal_500</item>
<item name="colorAccentDark">@color/md_teal_700</item>
<item name="cardBackgroundColor">@color/white</item>
<item name="android:colorBackground">@color/md_grey_50</item>
<item name="android:textColorPrimary">@color/md_grey_900</item>
<item name="android:textColorSecondary">@color/md_grey_400</item>
</style>
<style name="NoBarRedTealDark" parent="MaterialDrawerTheme">
<item name="colorPrimary">@color/md_red_500</item>
<item name="colorPrimaryDark">@color/md_red_700</item>
<item name="colorAccent">@color/md_teal_500</item>
<item name="colorAccentDark">@color/md_teal_700</item>
<item name="cardBackgroundColor">@color/md_grey_800</item>
<item name="android:colorBackground">#303030</item>
<item name="bnbBackgroundColor">@color/md_grey_900</item>
<item name="android:textColorPrimary">@color/md_white_1000</item>
<item name="android:textColorSecondary">@color/md_grey_600</item>
</style>
<style name="NoBarCyanPink" parent="MaterialDrawerTheme.Light">
<item name="colorPrimary">@color/md_cyan_500</item>
<item name="colorPrimaryDark">@color/md_cyan_700</item>
<item name="colorAccent">@color/md_pink_500</item>
<item name="colorAccentDark">@color/md_pink_700</item>
<item name="cardBackgroundColor">@color/white</item>
<item name="android:colorBackground">@color/md_grey_50</item>
<item name="android:textColorPrimary">@color/md_grey_900</item>
<item name="android:textColorSecondary">@color/md_grey_400</item>
</style>
<style name="NoBarCyanPinkDark" parent="MaterialDrawerTheme">
<item name="colorPrimary">@color/md_cyan_500</item>
<item name="colorPrimaryDark">@color/md_cyan_700</item>
<item name="colorAccent">@color/md_pink_500</item>
<item name="colorAccentDark">@color/md_pink_700</item>
<item name="cardBackgroundColor">@color/md_grey_800</item>
<item name="android:colorBackground">#303030</item>
<item name="bnbBackgroundColor">@color/md_grey_900</item>
<item name="android:textColorPrimary">@color/md_white_1000</item>
<item name="android:textColorSecondary">@color/md_grey_600</item>
</style>
<style name="NoBarTealOrange" parent="MaterialDrawerTheme.Light">
<item name="colorPrimary">@color/md_teal_500</item>
<item name="colorPrimaryDark">@color/md_teal_700</item>
<item name="colorAccent">@color/md_orange_500</item>
<item name="colorAccentDark">@color/md_orange_700</item>
<item name="cardBackgroundColor">@color/white</item>
<item name="android:colorBackground">@color/md_grey_50</item>
<item name="android:textColorPrimary">@color/md_grey_900</item>
<item name="android:textColorSecondary">@color/md_grey_400</item>
</style>
<style name="NoBarTealOrangeDark" parent="MaterialDrawerTheme">
<item name="colorPrimary">@color/md_teal_500</item>
<item name="colorPrimaryDark">@color/md_teal_700</item>
<item name="colorAccent">@color/md_orange_500</item>
<item name="colorAccentDark">@color/md_orange_700</item>
<item name="cardBackgroundColor">@color/md_grey_800</item>
<item name="android:colorBackground">#303030</item>
<item name="bnbBackgroundColor">@color/md_grey_900</item>
<item name="android:textColorPrimary">@color/md_white_1000</item>
<item name="android:textColorSecondary">@color/md_grey_600</item>
</style>
</resources> </resources>

View File

@ -5,6 +5,11 @@
android:icon="@drawable/ic_settings_black_24dp" android:icon="@drawable/ic_settings_black_24dp"
android:title="@string/pref_header_general"/> android:title="@string/pref_header_general"/>
<header
android:id="@+id/theme_change"
android:icon="@drawable/ic_color_lens_black_24dp"
android:title="@string/pref_header_theme"/>
<header <header
android:fragment="apps.amine.bou.readerforselfoss.settings.SettingsActivity$LinksPreferenceFragment" android:fragment="apps.amine.bou.readerforselfoss.settings.SettingsActivity$LinksPreferenceFragment"
android:icon="@drawable/ic_info_black_24" android:icon="@drawable/ic_info_black_24"

View File

@ -1,18 +1,18 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext.kotlin_version = '1.1.2-4' ext.kotlin_version = '1.1.3-2'
repositories { repositories {
maven { url 'https://maven.google.com' } maven { url 'https://maven.google.com' }
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.0.0-alpha3' classpath 'com.android.tools.build:gradle:3.0.0-alpha5'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files
classpath 'com.google.gms:google-services:3.0.0' classpath 'com.google.gms:google-services:3.1.0'
} }
} }
@ -30,3 +30,15 @@ allprojects {
task clean(type: Delete) { task clean(type: Delete) {
delete rootProject.buildDir delete rootProject.buildDir
} }
project.ext.preDexLibs = !project.hasProperty('disablePreDex')
subprojects {
project.plugins.whenPluginAdded { plugin ->
if ("com.android.build.gradle.AppPlugin".equals(plugin.class.name)) {
project.android.dexOptions.preDexLibraries = rootProject.ext.preDexLibs
} else if ("com.android.build.gradle.LibraryPlugin".equals(plugin.class.name)) {
project.android.dexOptions.preDexLibraries = rootProject.ext.preDexLibs
}
}
}

View File

@ -1,43 +0,0 @@
machine:
timezone: Europe/Paris
java:
version: 'oraclejdk8'
environment:
ANDROID_HOME: /usr/local/android-sdk-linux
PATH: ANDROID_HOME:$ANDROID_HOME/platform-tools:$ANDROID_BUUILD:$ANDROID_HOME/tools:$PATH
_JAVA_OPTIONS: "-Xms512m -Xmx1024m"
GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError"'
dependencies:
pre:
- touch app/google-services.json
- echo $GOOGLE_SERVICES_JSON > app/google-services.json
- touch app/src/main/res/values/secrets.xml
- echo $SECRETS_XML > app/src/main/res/values/secrets.xml
- mkdir app/src/main/res/mipmap-hdpi
- convert -size 72x72 xc:white app/src/main/res/mipmap-hdpi/ic_launcher.png
- mkdir app/src/main/res/mipmap-mdpi
- convert -size 48x48 xc:white app/src/main/res/mipmap-mdpi/ic_launcher.png
- mkdir app/src/main/res/mipmap-xhdpi
- convert -size 96x96 xc:white app/src/main/res/mipmap-xhdpi/ic_launcher.png
- mkdir app/src/main/res/mipmap-xxhdpi
- convert -size 192x192 xc:white app/src/main/res/mipmap-xxhdpi/ic_launcher.png
- mkdir app/src/main/res/mipmap-xxxhdpi
- convert -size 512x512 xc:white app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
cache_directories:
- ~/.gradle
- ~/.android
override:
- echo y | android update sdk --no-ui --filter "android-25,build-tools-25.0.3"
- echo y | android update sdk --no-ui --all --filter "platform-tools, tools"
- echo y | android update sdk --no-ui --all --filter "android-25, build-tools-25.0.3"
- echo y | android update sdk --no-ui --all --filter "extra-android-m2repository"
- echo y | android update sdk --no-ui --all --filter "extra-android-support"
- echo y | android update sdk --no-ui --all --filter "extra-google-m2repository"
- echo y | android update sdk --no-ui --all --filter "extra-google-google_play_services"
test:
override:
- gradlew assemble -P crashlyticsdemoApikey=$FABRIC_API_KEY -P crashlyticsdemoApisecret=$FABRIC_API_SECRET

View File

@ -1,6 +1,6 @@
#Sat May 27 22:06:05 CEST 2017 #Sun Jul 02 08:13:37 CEST 2017
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-milestone-1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-milestone-1-all.zip

BIN
secrets.tar.enc Normal file

Binary file not shown.