Compare commits
48 Commits
Author | SHA1 | Date | |
---|---|---|---|
1d18c898b2 | |||
95e208000f | |||
ecdddef81d | |||
c9b1d329e6 | |||
e68c16c7a4 | |||
585c57fe3a | |||
d04cbac79c | |||
044585ee9b | |||
299478e840 | |||
b2d69be5f8 | |||
dc970bbf3c | |||
8717bd5d5d | |||
5b307a8407 | |||
daef66087d | |||
1ad1cf4460 | |||
c0b9718368 | |||
d684f323b8 | |||
24a1c56fe6 | |||
cdeba4f84e | |||
cafba196cf | |||
493b1b12b3 | |||
5320f88230 | |||
246ec2c3ac | |||
9c9b45aeab | |||
8c5dc43735 | |||
b1e812314f | |||
c14f47a74b | |||
58a5b4a5e5 | |||
1cfc2bf36f | |||
5a56d826d9 | |||
8ad8b55424 | |||
3da1d431db | |||
4565079f29 | |||
3482092cb2 | |||
2df5e52de0 | |||
0ef4fc67fa | |||
e2bfd549d3 | |||
7071af5fa5 | |||
95f267f701 | |||
f363bbcc0c | |||
65a912f271 | |||
758661f5fb | |||
2d3c297726 | |||
ca85e3d3ed | |||
2190ad0387 | |||
0d067e05af | |||
c3305b7523 | |||
fb84b31122 |
55
.github/CONTRIBUTING.md
vendored
Normal file
55
.github/CONTRIBUTING.md
vendored
Normal 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
32
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
### Prerequisites
|
||||
|
||||
* [ ] Are you running the latest version?
|
||||
* [ ] Did you check for an existing issue ?
|
||||
* [ ] Are you reporting to the correct repository?
|
||||
* [ ] Did you perform a cursory search?
|
||||
* [ ] Did you read the `CONTRIBUTING` guide ?
|
||||
|
||||
### Description
|
||||
|
||||
[Description of the bug or feature]
|
||||
|
||||
### Steps to Reproduce
|
||||
|
||||
1. [First Step]
|
||||
2. [Second Step]
|
||||
3. [and so on...]
|
||||
|
||||
**Expected behavior:** [What you expected to happen]
|
||||
|
||||
**Actual behavior:** [What actually happened]
|
||||
|
||||
|
||||
### Screenshots (optional)
|
||||
|
||||
`...`
|
||||
|
||||
### Device
|
||||
|
||||
- Device (manufacturer, model ...)
|
||||
- OS (Android Version, ROM/Stock, Rooted/not, mods...)
|
||||
- App version _(See Prerequisites)_
|
13
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
13
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
## Types of changes
|
||||
|
||||
- [ ] I have read the **CONTRIBUTING** document.
|
||||
- [ ] My code follows the code style of this project.
|
||||
- [ ] I have updated the documentation accordingly.
|
||||
- [ ] I have added tests to cover my changes.
|
||||
- [ ] All new and existing tests passed.
|
||||
|
||||
This closes issue #XXX
|
||||
|
||||
This is implements feature #YYY
|
||||
|
||||
This finishes chore #ZZZ
|
35
CHANGELOG.md
35
CHANGELOG.md
@ -1,3 +1,38 @@
|
||||
**1.5.1.3**
|
||||
|
||||
- Fixes introduces by the previous alpha (1.5.1.2)
|
||||
|
||||
**1.5.1.2**
|
||||
|
||||
- Added testing to the CI.
|
||||
|
||||
- Code cleaning
|
||||
|
||||
- Display the pull to refresh loader on api call
|
||||
|
||||
- Fixes :
|
||||
|
||||
- Can't pull down to refresh on first launch
|
||||
|
||||
- Recurring crash because of the url
|
||||
|
||||
- Couldn't open some urls because of missing "http"
|
||||
|
||||
- Adding a source with invalid url would crash
|
||||
|
||||
|
||||
**1.5.1.1**
|
||||
|
||||
- Fixed an issue when trying to add a source without being logged in.
|
||||
|
||||
- Reloading drawer tags badges on slide to refresh.
|
||||
|
||||
**1.5.1**
|
||||
|
||||
- Added a drawer for filtering sources and tags.
|
||||
|
||||
- You can now search for items from the toolbar.
|
||||
|
||||
**1.5.0.2**
|
||||
|
||||
- If the content in the article viewer is empty, the article will open in a custom tab.
|
||||
|
20
README.md
20
README.md
@ -2,26 +2,18 @@
|
||||
|
||||
[](https://circleci.com/gh/aminecmi/ReaderforSelfoss/tree/master)
|
||||
|
||||
[](https://codebeat.co/projects/github-com-aminecmi-readerforselfoss-master)
|
||||
|
||||
[](https://www.codetriage.com/aminecmi/readerforselfoss)
|
||||
|
||||
This is the repo of [Reader For Selfoss](https://play.google.com/store/apps/details?id=apps.amine.bou.readerforselfoss&hl=en).
|
||||
|
||||
It's an RSS Reader for Android, that **only** works with [Selfoss](https://selfoss.aditu.de/)
|
||||
|
||||
|
||||
## Build
|
||||
## Want to help ?
|
||||
|
||||
You can directly import this project into IntellIJ/Android Studio.
|
||||
|
||||
You'll have to:
|
||||
|
||||
- [Create your own launcher icon](https://developer.android.com/studio/write/image-asset-studio.html#creating-launcher)
|
||||
|
||||
- Configure Fabric, or [remove it](https://docs.fabric.io/android/fabric/settings/removing.html#).
|
||||
- Define the following in `res/values/strings.xml` or create `res/values/secrets.xml`
|
||||
|
||||
- mercury: A [Mercury](https://mercury.postlight.com/web-parser/) web parser api key for the internal browser
|
||||
- feedback_email: An email to receive users feedback.
|
||||
- source_url: an url to the source code, used in the settings
|
||||
- tracker_url: an url to the tracker, used in the settings
|
||||
Check the [Contribution guide](https://github.com/aminecmi/ReaderforSelfoss/blob/master/.github/CONTRIBUTING.md)
|
||||
|
||||
## Useful links
|
||||
|
||||
|
@ -19,14 +19,14 @@ repositories {
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 25
|
||||
buildToolsVersion "25.0.3"
|
||||
compileSdkVersion 26
|
||||
buildToolsVersion "26.0.0"
|
||||
defaultConfig {
|
||||
applicationId "apps.amine.bou.readerforselfoss"
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 25
|
||||
versionCode 1502
|
||||
versionName "1.5.0.2"
|
||||
versionCode 1513
|
||||
versionName "1.5.1.3"
|
||||
|
||||
// Enabling multidex support.
|
||||
multiDexEnabled true
|
||||
@ -35,6 +35,9 @@ android {
|
||||
disable 'InvalidPackage'
|
||||
}
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
|
||||
// tests
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
@ -42,6 +45,11 @@ android {
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'),
|
||||
'proguard-rules.pro'
|
||||
}
|
||||
debug {
|
||||
buildConfigField "String", "LOGIN_URL", appLoginUrl
|
||||
buildConfigField "String", "LOGIN_USERNAME", appLoginUsername
|
||||
buildConfigField "String", "LOGIN_PASSWORD", appLoginPassword
|
||||
}
|
||||
}
|
||||
flavorDimensions "build"
|
||||
productFlavors {
|
||||
@ -59,23 +67,32 @@ android {
|
||||
}
|
||||
|
||||
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 "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
|
||||
|
||||
// Android Support
|
||||
compile 'com.android.support:appcompat-v7:25.3.1'
|
||||
compile 'com.android.support:design:25.3.1'
|
||||
compile 'com.android.support:recyclerview-v7:25.3.1'
|
||||
compile 'com.android.support:support-v4:25.3.1'
|
||||
compile 'com.android.support:support-vector-drawable:25.3.1'
|
||||
compile 'com.android.support:customtabs:25.3.1'
|
||||
compile 'com.android.support:cardview-v7:25.3.1'
|
||||
compile 'com.android.support:appcompat-v7:26.0.0-beta2'
|
||||
compile 'com.android.support:design:26.0.0-beta2'
|
||||
compile 'com.android.support:recyclerview-v7:26.0.0-beta2'
|
||||
compile 'com.android.support:support-v4:26.0.0-beta2'
|
||||
compile 'com.android.support:support-vector-drawable:26.0.0-beta2'
|
||||
compile 'com.android.support:customtabs:26.0.0-beta2'
|
||||
compile 'com.android.support:cardview-v7:26.0.0-beta2'
|
||||
compile 'com.android.support.constraint:constraint-layout:1.0.2'
|
||||
|
||||
// Firebase + crashlytics
|
||||
compile 'com.google.firebase:firebase-core:10.2.6'
|
||||
compile 'com.google.firebase:firebase-config:10.2.6'
|
||||
compile 'com.google.firebase:firebase-invites:10.2.6'
|
||||
compile 'com.google.firebase:firebase-core:11.0.1'
|
||||
compile 'com.google.firebase:firebase-config:11.0.1'
|
||||
compile 'com.google.firebase:firebase-invites:11.0.1'
|
||||
compile('com.crashlytics.sdk.android:crashlytics:2.6.8@aar') {
|
||||
transitive = true
|
||||
}
|
||||
@ -98,7 +115,7 @@ dependencies {
|
||||
compile 'com.burgstaller:okhttp-digest:1.12'
|
||||
|
||||
// Material-ish things
|
||||
compile 'com.roughike:bottom-bar:2.2.0'
|
||||
compile 'com.roughike:bottom-bar:2.3.1'
|
||||
compile 'com.melnykov:floatingactionbutton:1.3.0'
|
||||
compile 'com.github.jd-alexander:LikeButton:0.2.1'
|
||||
compile 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
|
||||
@ -111,7 +128,13 @@ dependencies {
|
||||
compile 'com.github.stkent:amplify:1.5.0'
|
||||
|
||||
// For the article reader
|
||||
compile 'com.klinkerapps:drag-dismiss-activity:1.4.0'
|
||||
compile 'com.klinkerapps:drag-dismiss-activity:1.4.1'
|
||||
|
||||
// Drawer
|
||||
compile('com.mikepenz:materialdrawer:5.9.3@aar') {
|
||||
transitive = true
|
||||
}
|
||||
compile 'com.anupcowkur:reservoir:3.1.0'
|
||||
|
||||
}
|
||||
|
||||
@ -120,6 +143,7 @@ apply plugin: 'com.google.gms.google-services'
|
||||
|
||||
afterEvaluate {
|
||||
initFabricPropertiesIfNeeded()
|
||||
initAppLoginPropertiesIfNeeded()
|
||||
}
|
||||
|
||||
def initFabricPropertiesIfNeeded() {
|
||||
@ -131,4 +155,16 @@ def initFabricPropertiesIfNeeded() {
|
||||
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"))
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
package apps.amine.bou.readerforselfoss
|
||||
|
||||
|
||||
// TODO: test source adding
|
@ -0,0 +1,123 @@
|
||||
package apps.amine.bou.readerforselfoss
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.support.test.InstrumentationRegistry
|
||||
import android.support.test.espresso.Espresso.onView
|
||||
import android.support.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
|
||||
import android.support.test.espresso.action.ViewActions.*
|
||||
import android.support.test.espresso.assertion.ViewAssertions.matches
|
||||
import android.support.test.espresso.contrib.DrawerActions
|
||||
import android.support.test.espresso.intent.Intents
|
||||
import android.support.test.espresso.intent.Intents.intended
|
||||
import android.support.test.espresso.intent.Intents.times
|
||||
import android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent
|
||||
import android.support.test.espresso.matcher.ViewMatchers.*
|
||||
import android.support.test.rule.ActivityTestRule
|
||||
import android.support.test.runner.AndroidJUnit4
|
||||
import android.view.KeyEvent
|
||||
|
||||
import com.mikepenz.aboutlibraries.ui.LibsActivity
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import apps.amine.bou.readerforselfoss.settings.SettingsActivity
|
||||
import apps.amine.bou.readerforselfoss.utils.Config
|
||||
import org.junit.After
|
||||
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class HomeActivityEspressoTest {
|
||||
lateinit var context: Context
|
||||
|
||||
@Rule @JvmField
|
||||
val rule = ActivityTestRule(HomeActivity::class.java, true, false)
|
||||
|
||||
@Before
|
||||
fun clearData() {
|
||||
context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
|
||||
val editor =
|
||||
context
|
||||
.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
|
||||
.edit()
|
||||
editor.clear()
|
||||
|
||||
editor.putString("url", BuildConfig.LOGIN_URL)
|
||||
editor.putString("login", BuildConfig.LOGIN_USERNAME)
|
||||
editor.putString("password", BuildConfig.LOGIN_PASSWORD)
|
||||
|
||||
editor.commit()
|
||||
|
||||
Intents.init()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun menuItems() {
|
||||
|
||||
rule.launchActivity(Intent())
|
||||
|
||||
onView(
|
||||
withMenu(
|
||||
id = R.id.action_search,
|
||||
titleId = R.string.menu_home_search
|
||||
)
|
||||
).perform(click())
|
||||
|
||||
onView(withId(R.id.search_bar)).check(matches(isDisplayed()))
|
||||
|
||||
onView(withId(R.id.search_src_text)).perform(typeText("android"), pressKey(KeyEvent.KEYCODE_SEARCH), closeSoftKeyboard())
|
||||
|
||||
onView(withContentDescription(R.string.abc_toolbar_collapse_description)).perform(click())
|
||||
|
||||
|
||||
onView(withMenu(id = R.id.readAll, titleId = R.string.readAll)).perform(click())
|
||||
|
||||
openActionBarOverflowOrOptionsMenu(context)
|
||||
|
||||
onView(withMenu(id = R.id.refresh, titleId = R.string.menu_home_refresh))
|
||||
.perform(click())
|
||||
|
||||
openActionBarOverflowOrOptionsMenu(context)
|
||||
|
||||
onView(withText(R.string.action_disconnect)).perform(click())
|
||||
|
||||
intended(hasComponent(LoginActivity::class.java.name), times(1))
|
||||
|
||||
onView(isRoot()).perform(pressBack())
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun drawerTesting() {
|
||||
|
||||
rule.launchActivity(Intent())
|
||||
|
||||
onView(withId(R.id.material_drawer_layout)).perform(DrawerActions.open())
|
||||
|
||||
onView(withText(R.string.action_about)).perform(click())
|
||||
intended(hasComponent(LibsActivity::class.java.name))
|
||||
onView(isRoot()).perform(pressBack())
|
||||
intended(hasComponent(HomeActivity::class.java.name))
|
||||
|
||||
onView(withId(R.id.material_drawer_layout)).perform(DrawerActions.open())
|
||||
|
||||
onView(withId(R.id.material_drawer_layout)).perform(DrawerActions.open())
|
||||
onView(withText(R.string.drawer_action_clear)).perform(click())
|
||||
|
||||
// bug
|
||||
//onView(withText(R.string.title_activity_settings)).perform(scrollTo(), click())
|
||||
//intended(hasComponent(SettingsActivity::class.java.name))
|
||||
|
||||
}
|
||||
|
||||
// TODO: test articles opening and actions for cards and lists
|
||||
|
||||
@After
|
||||
fun releaseIntents() {
|
||||
Intents.release()
|
||||
}
|
||||
}
|
@ -0,0 +1,121 @@
|
||||
package apps.amine.bou.readerforselfoss
|
||||
|
||||
import java.util.*
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.support.test.InstrumentationRegistry.getInstrumentation
|
||||
import android.support.test.espresso.Espresso.onView
|
||||
import android.support.test.espresso.action.ViewActions.click
|
||||
import android.support.test.espresso.assertion.ViewAssertions.matches
|
||||
import android.support.test.espresso.intent.Intents
|
||||
import android.support.test.espresso.intent.Intents.intended
|
||||
import android.support.test.espresso.intent.Intents.times
|
||||
import android.support.test.espresso.intent.matcher.IntentMatchers.*
|
||||
import android.support.test.espresso.intent.matcher.UriMatchers.hasHost
|
||||
import android.support.test.espresso.matcher.ViewMatchers.*
|
||||
import android.support.test.rule.ActivityTestRule
|
||||
import android.support.test.runner.AndroidJUnit4
|
||||
|
||||
import org.hamcrest.Matchers.allOf
|
||||
import org.hamcrest.Matchers.equalTo
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import apps.amine.bou.readerforselfoss.utils.Config
|
||||
import org.junit.After
|
||||
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class IntroActivityEspressoTest {
|
||||
|
||||
@Rule @JvmField
|
||||
val rule = ActivityTestRule(IntroActivity::class.java, true, false)
|
||||
|
||||
@Before
|
||||
fun clearData() {
|
||||
val editor =
|
||||
getInstrumentation().targetContext
|
||||
.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
|
||||
.edit()
|
||||
editor.clear()
|
||||
editor.commit()
|
||||
|
||||
Intents.init()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nextEachTimes() {
|
||||
|
||||
rule.launchActivity(Intent())
|
||||
|
||||
onView(withText(R.string.intro_hello_title)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.button_next)).perform(click())
|
||||
onView(withText(R.string.intro_needs_selfoss_message)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.button_next)).perform(click())
|
||||
onView(withText(R.string.intro_all_set_message)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.button_next)).perform(click())
|
||||
|
||||
intended(hasComponent(IntroActivity::class.java.name), times(1))
|
||||
intended(hasComponent(LoginActivity::class.java.name), times(1))
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nextBackRandomTimes() {
|
||||
val max = 5
|
||||
val min = 1
|
||||
|
||||
val random = (Random().nextInt(max + 1 - min)) + min
|
||||
|
||||
rule.launchActivity(Intent())
|
||||
|
||||
onView(withText(R.string.intro_hello_title)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.button_next)).perform(click())
|
||||
|
||||
repeat(random) {_ ->
|
||||
onView(withText(R.string.intro_needs_selfoss_message)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.button_next)).perform(click())
|
||||
onView(withText(R.string.intro_all_set_message)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.button_back)).perform(click())
|
||||
}
|
||||
|
||||
onView(withId(R.id.button_next)).perform(click())
|
||||
onView(withText(R.string.intro_all_set_message)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.button_next)).perform(click())
|
||||
|
||||
intended(hasComponent(IntroActivity::class.java.name), times(1))
|
||||
intended(hasComponent(LoginActivity::class.java.name), times(1))
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun clickSelfossUrl() {
|
||||
rule.launchActivity(Intent())
|
||||
|
||||
onView(withText(R.string.intro_hello_title)).check(matches(isDisplayed()))
|
||||
|
||||
onView(withId(R.id.button_next)).perform(click())
|
||||
|
||||
onView(withId(R.id.button_message)).perform(click())
|
||||
|
||||
intended(
|
||||
allOf(
|
||||
hasData(
|
||||
hasHost(
|
||||
equalTo("selfoss.aditu.de")
|
||||
)
|
||||
),
|
||||
hasAction(Intent.ACTION_VIEW)
|
||||
)
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@After
|
||||
fun releaseIntents() {
|
||||
Intents.release()
|
||||
}
|
||||
}
|
@ -0,0 +1,164 @@
|
||||
package apps.amine.bou.readerforselfoss
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.support.test.InstrumentationRegistry
|
||||
import android.support.test.espresso.Espresso.onView
|
||||
import android.support.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
|
||||
import android.support.test.espresso.action.ViewActions.*
|
||||
import android.support.test.espresso.assertion.ViewAssertions.matches
|
||||
import android.support.test.espresso.intent.Intents
|
||||
import android.support.test.espresso.intent.Intents.intended
|
||||
import android.support.test.espresso.intent.Intents.times
|
||||
import android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent
|
||||
import android.support.test.espresso.matcher.ViewMatchers
|
||||
import android.support.test.espresso.matcher.ViewMatchers.*
|
||||
import android.support.test.rule.ActivityTestRule
|
||||
import android.support.test.runner.AndroidJUnit4
|
||||
|
||||
import com.mikepenz.aboutlibraries.ui.LibsActivity
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import apps.amine.bou.readerforselfoss.utils.Config
|
||||
import org.junit.After
|
||||
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class LoginActivityEspressoTest {
|
||||
|
||||
@Rule @JvmField
|
||||
val rule = ActivityTestRule(LoginActivity::class.java, true, false)
|
||||
|
||||
lateinit var context: Context
|
||||
lateinit var url: String
|
||||
lateinit var username: String
|
||||
lateinit var password: String
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
val editor =
|
||||
context
|
||||
.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
|
||||
.edit()
|
||||
editor.clear()
|
||||
editor.commit()
|
||||
|
||||
|
||||
url = BuildConfig.LOGIN_URL
|
||||
username = BuildConfig.LOGIN_USERNAME
|
||||
password = BuildConfig.LOGIN_PASSWORD
|
||||
|
||||
Intents.init()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun menuItems() {
|
||||
|
||||
rule.launchActivity(Intent())
|
||||
|
||||
openActionBarOverflowOrOptionsMenu(context)
|
||||
|
||||
onView(withText(R.string.action_about)).perform(click())
|
||||
|
||||
intended(hasComponent(LibsActivity::class.java.name), times(1))
|
||||
|
||||
onView(isRoot()).perform(pressBack())
|
||||
|
||||
intended(hasComponent(LoginActivity::class.java.name))
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun wrongLoginUrl() {
|
||||
rule.launchActivity(Intent())
|
||||
|
||||
onView(withId(R.id.login_progress))
|
||||
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)))
|
||||
|
||||
onView(withId(R.id.url)).perform(click()).perform(typeText("WRONGURL"))
|
||||
|
||||
onView(withId(R.id.email_sign_in_button)).perform(click())
|
||||
|
||||
onView(withId(R.id.urlLayout)).check(matches(isHintOrErrorEnabled()))
|
||||
}
|
||||
|
||||
// TODO: Add tests for multiple false urls with dialog
|
||||
|
||||
@Test
|
||||
fun emptyAuthData() {
|
||||
|
||||
rule.launchActivity(Intent())
|
||||
|
||||
onView(withId(R.id.url)).perform(click()).perform(typeText(url), closeSoftKeyboard())
|
||||
|
||||
onView(withId(R.id.withLogin)).perform(click())
|
||||
|
||||
onView(withId(R.id.email_sign_in_button)).perform(click())
|
||||
|
||||
onView(withId(R.id.loginLayout)).check(matches(isHintOrErrorEnabled()))
|
||||
onView(withId(R.id.passwordLayout)).check(matches(isHintOrErrorEnabled()))
|
||||
|
||||
onView(withId(R.id.login)).perform(click()).perform(typeText(username), closeSoftKeyboard())
|
||||
|
||||
onView(withId(R.id.passwordLayout)).check(matches(isHintOrErrorEnabled()))
|
||||
|
||||
onView(withId(R.id.email_sign_in_button)).perform(click())
|
||||
|
||||
onView(withId(R.id.passwordLayout)).check(
|
||||
matches(
|
||||
isHintOrErrorEnabled())
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun wrongAuthData() {
|
||||
|
||||
rule.launchActivity(Intent())
|
||||
|
||||
onView(withId(R.id.url)).perform(click()).perform(typeText(url), closeSoftKeyboard())
|
||||
|
||||
onView(withId(R.id.withLogin)).perform(click())
|
||||
|
||||
onView(withId(R.id.login)).perform(click()).perform(typeText(username), closeSoftKeyboard())
|
||||
|
||||
onView(withId(R.id.password)).perform(click()).perform(typeText("WRONGPASS"), closeSoftKeyboard())
|
||||
|
||||
onView(withId(R.id.email_sign_in_button)).perform(click())
|
||||
|
||||
onView(withId(R.id.urlLayout)).check(matches(isHintOrErrorEnabled()))
|
||||
onView(withId(R.id.loginLayout)).check(matches(isHintOrErrorEnabled()))
|
||||
onView(withId(R.id.passwordLayout)).check(matches(isHintOrErrorEnabled()))
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun workingAuth() {
|
||||
|
||||
rule.launchActivity(Intent())
|
||||
|
||||
onView(withId(R.id.url)).perform(click()).perform(typeText(url), closeSoftKeyboard())
|
||||
|
||||
onView(withId(R.id.withLogin)).perform(click())
|
||||
|
||||
onView(withId(R.id.login)).perform(click()).perform(typeText(username), closeSoftKeyboard())
|
||||
|
||||
onView(withId(R.id.password)).perform(click()).perform(typeText(password), closeSoftKeyboard())
|
||||
|
||||
onView(withId(R.id.email_sign_in_button)).perform(click())
|
||||
|
||||
intended(hasComponent(HomeActivity::class.java.name))
|
||||
|
||||
}
|
||||
|
||||
@After
|
||||
fun releaseIntents() {
|
||||
Intents.release()
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
package apps.amine.bou.readerforselfoss
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.preference.PreferenceManager
|
||||
import android.support.test.InstrumentationRegistry.getInstrumentation
|
||||
import android.support.test.espresso.intent.Intents
|
||||
import android.support.test.espresso.intent.Intents.intended
|
||||
import android.support.test.espresso.intent.Intents.times
|
||||
import android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent
|
||||
import android.support.test.rule.ActivityTestRule
|
||||
import android.support.test.runner.AndroidJUnit4
|
||||
import org.junit.After
|
||||
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class MainActivityEspressoTest {
|
||||
|
||||
lateinit var intent: Intent
|
||||
lateinit var preferencesEditor: SharedPreferences.Editor
|
||||
|
||||
@Rule @JvmField
|
||||
val rule = ActivityTestRule(MainActivity::class.java, true, false)
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
intent = Intent()
|
||||
val context = getInstrumentation().targetContext
|
||||
|
||||
// create a SharedPreferences editor
|
||||
preferencesEditor = PreferenceManager.getDefaultSharedPreferences(context).edit()
|
||||
|
||||
Intents.init()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkFirstOpenLaunchesIntro() {
|
||||
preferencesEditor.putBoolean("firstStart", true)
|
||||
preferencesEditor.commit()
|
||||
|
||||
rule.launchActivity(intent)
|
||||
|
||||
intended(hasComponent(MainActivity::class.java.name))
|
||||
intended(hasComponent(IntroActivity::class.java.name))
|
||||
intended(hasComponent(LoginActivity::class.java.name), times(0))
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkNotFirstOpenLaunchesLogin() {
|
||||
preferencesEditor.putBoolean("firstStart", false)
|
||||
preferencesEditor.commit()
|
||||
|
||||
rule.launchActivity(intent)
|
||||
|
||||
intended(hasComponent(MainActivity::class.java.name))
|
||||
intended(hasComponent(LoginActivity::class.java.name))
|
||||
intended(hasComponent(IntroActivity::class.java.name), times(0))
|
||||
|
||||
}
|
||||
|
||||
@After
|
||||
fun releaseIntents() {
|
||||
Intents.release()
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package apps.amine.bou.readerforselfoss
|
||||
|
||||
import android.view.View
|
||||
import android.support.design.widget.TextInputLayout
|
||||
import android.support.test.espresso.matcher.ViewMatchers
|
||||
|
||||
import org.hamcrest.Description
|
||||
import org.hamcrest.Matcher
|
||||
import org.hamcrest.TypeSafeMatcher
|
||||
import org.hamcrest.Matchers
|
||||
|
||||
|
||||
|
||||
fun isHintOrErrorEnabled(): Matcher<View> =
|
||||
object : TypeSafeMatcher<View>() {
|
||||
override fun describeTo(description: Description?) {}
|
||||
|
||||
override fun matchesSafely(item: View?): Boolean {
|
||||
if (item !is TextInputLayout) {
|
||||
return false
|
||||
}
|
||||
|
||||
return item.isHintEnabled || item.isErrorEnabled
|
||||
}
|
||||
}
|
||||
|
||||
fun withMenu(id: Int, titleId: Int): Matcher<View> =
|
||||
Matchers.anyOf(
|
||||
ViewMatchers.withId(id),
|
||||
ViewMatchers.withText(titleId)
|
||||
)
|
@ -31,7 +31,8 @@
|
||||
<activity android:name=".LoginActivity"
|
||||
android:label="@string/title_activity_login">
|
||||
</activity>
|
||||
<activity android:name=".HomeActivity">
|
||||
<activity android:name=".HomeActivity"
|
||||
android:theme="@style/NoBar">
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".settings.SettingsActivity"
|
||||
|
@ -6,14 +6,17 @@ import android.support.constraint.ConstraintLayout
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.view.View
|
||||
import android.widget.*
|
||||
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.Spout
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
|
||||
import apps.amine.bou.readerforselfoss.utils.Config
|
||||
import apps.amine.bou.readerforselfoss.utils.isUrlValid
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
|
||||
|
||||
|
||||
class AddSourceActivity : AppCompatActivity() {
|
||||
@ -24,15 +27,20 @@ class AddSourceActivity : AppCompatActivity() {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_add_source)
|
||||
|
||||
val mProgress = findViewById(R.id.progress) as ProgressBar
|
||||
val mForm = findViewById(R.id.formContainer) as ConstraintLayout
|
||||
val mNameInput = findViewById(R.id.nameInput) as EditText
|
||||
val mSourceUri = findViewById(R.id.sourceUri) as EditText
|
||||
val mTags = findViewById(R.id.tags) as EditText
|
||||
val mSpoutsSpinner = findViewById(R.id.spoutsSpinner) as Spinner
|
||||
val mSaveBtn = findViewById(R.id.saveBtn) as Button
|
||||
val api = SelfossApi(this)
|
||||
val mProgress: ProgressBar = findViewById(R.id.progress)
|
||||
val mForm: ConstraintLayout = findViewById(R.id.formContainer)
|
||||
val mNameInput: EditText = findViewById(R.id.nameInput)
|
||||
val mSourceUri: EditText = findViewById(R.id.sourceUri)
|
||||
val mTags: EditText = findViewById(R.id.tags)
|
||||
val mSpoutsSpinner: Spinner = findViewById(R.id.spoutsSpinner)
|
||||
val mSaveBtn: Button = findViewById(R.id.saveBtn)
|
||||
var api: SelfossApi? = null
|
||||
|
||||
try {
|
||||
api = SelfossApi(this, this@AddSourceActivity)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
mustLoginToAddSource()
|
||||
}
|
||||
|
||||
val intent = intent
|
||||
if (Intent.ACTION_SEND == intent.action && "text/plain" == intent.type) {
|
||||
@ -40,7 +48,9 @@ class AddSourceActivity : AppCompatActivity() {
|
||||
mNameInput.setText(intent.getStringExtra(Intent.EXTRA_TITLE))
|
||||
}
|
||||
|
||||
mSaveBtn.setOnClickListener { handleSaveSource(mTags, mNameInput.text.toString(), mSourceUri.text.toString(), api) }
|
||||
mSaveBtn.setOnClickListener {
|
||||
handleSaveSource(mTags, mNameInput.text.toString(), mSourceUri.text.toString(), api!!)
|
||||
}
|
||||
|
||||
|
||||
val spoutsKV = HashMap<String, String>()
|
||||
@ -57,15 +67,12 @@ class AddSourceActivity : AppCompatActivity() {
|
||||
|
||||
val config = Config(this)
|
||||
|
||||
if (config.baseUrl.isEmpty() || !isUrlValid(config.baseUrl)) {
|
||||
Toast.makeText(this, getString(R.string.addStringNoUrl), Toast.LENGTH_SHORT).show()
|
||||
val i = Intent(this, LoginActivity::class.java)
|
||||
startActivity(i)
|
||||
finish()
|
||||
if (config.baseUrl.isEmpty() || !config.baseUrl.isUrlValid()) {
|
||||
mustLoginToAddSource()
|
||||
} else {
|
||||
|
||||
var items: Map<String, Spout>
|
||||
api.spouts().enqueue(object : Callback<Map<String, Spout>> {
|
||||
api!!.spouts().enqueue(object : Callback<Map<String, Spout>> {
|
||||
override fun onResponse(call: Call<Map<String, Spout>>, response: Response<Map<String, Spout>>) {
|
||||
if (response.body() != null) {
|
||||
items = response.body()!!
|
||||
@ -78,7 +85,11 @@ class AddSourceActivity : AppCompatActivity() {
|
||||
mProgress.visibility = View.GONE
|
||||
mForm.visibility = View.VISIBLE
|
||||
|
||||
val spinnerArrayAdapter = ArrayAdapter(this@AddSourceActivity, android.R.layout.simple_spinner_item, itemsStrings)
|
||||
val spinnerArrayAdapter =
|
||||
ArrayAdapter(
|
||||
this@AddSourceActivity,
|
||||
android.R.layout.simple_spinner_item,
|
||||
itemsStrings)
|
||||
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
mSpoutsSpinner.adapter = spinnerArrayAdapter
|
||||
|
||||
@ -99,12 +110,25 @@ class AddSourceActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun mustLoginToAddSource() {
|
||||
Toast.makeText(this, getString(R.string.addStringNoUrl), Toast.LENGTH_SHORT).show()
|
||||
val i = Intent(this, LoginActivity::class.java)
|
||||
startActivity(i)
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun handleSaveSource(mTags: EditText, title: String, url: String, api: SelfossApi) {
|
||||
|
||||
if (title.isEmpty() || url.isEmpty() || mSpoutsValue == null || mSpoutsValue!!.isEmpty()) {
|
||||
Toast.makeText(this, R.string.form_not_complete, Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
api.createSource(title, url, mSpoutsValue!!, mTags.text.toString(), "").enqueue(object : Callback<SuccessResponse> {
|
||||
api.createSource(
|
||||
title,
|
||||
url,
|
||||
mSpoutsValue!!,
|
||||
mTags.text.toString(),
|
||||
""
|
||||
).enqueue(object : Callback<SuccessResponse> {
|
||||
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {
|
||||
if (response.body() != null && response.body()!!.isSuccess) {
|
||||
finish()
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,14 +1,16 @@
|
||||
package apps.amine.bou.readerforselfoss
|
||||
|
||||
import agency.tango.materialintroscreen.MaterialIntroActivity
|
||||
import agency.tango.materialintroscreen.MessageButtonBehaviour
|
||||
import agency.tango.materialintroscreen.SlideFragmentBuilder
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.preference.PreferenceManager
|
||||
import android.view.View
|
||||
|
||||
import agency.tango.materialintroscreen.MaterialIntroActivity
|
||||
import agency.tango.materialintroscreen.MessageButtonBehaviour
|
||||
import agency.tango.materialintroscreen.SlideFragmentBuilder
|
||||
|
||||
|
||||
|
||||
class IntroActivity : MaterialIntroActivity() {
|
||||
|
||||
@ -16,32 +18,32 @@ class IntroActivity : MaterialIntroActivity() {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
addSlide(SlideFragmentBuilder()
|
||||
.backgroundColor(R.color.colorPrimary)
|
||||
.buttonsColor(R.color.colorAccent)
|
||||
.image(R.mipmap.ic_launcher)
|
||||
.title(getString(R.string.intro_hello_title))
|
||||
.description(getString(R.string.intro_hello_message))
|
||||
.build())
|
||||
.backgroundColor(R.color.colorPrimary)
|
||||
.buttonsColor(R.color.colorAccent)
|
||||
.image(R.mipmap.ic_launcher)
|
||||
.title(getString(R.string.intro_hello_title))
|
||||
.description(getString(R.string.intro_hello_message))
|
||||
.build())
|
||||
|
||||
addSlide(SlideFragmentBuilder()
|
||||
.backgroundColor(R.color.colorAccent)
|
||||
.buttonsColor(R.color.colorPrimary)
|
||||
.image(R.drawable.ic_info_outline_white_48dp)
|
||||
.title(getString(R.string.intro_needs_selfoss_title))
|
||||
.description(getString(R.string.intro_needs_selfoss_message))
|
||||
.build(),
|
||||
MessageButtonBehaviour(View.OnClickListener {
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://selfoss.aditu.de"))
|
||||
startActivity(browserIntent)
|
||||
}, getString(R.string.intro_needs_selfoss_link)))
|
||||
.backgroundColor(R.color.colorAccent)
|
||||
.buttonsColor(R.color.colorPrimary)
|
||||
.image(R.drawable.ic_info_outline_white_48dp)
|
||||
.title(getString(R.string.intro_needs_selfoss_title))
|
||||
.description(getString(R.string.intro_needs_selfoss_message))
|
||||
.build(),
|
||||
MessageButtonBehaviour(View.OnClickListener {
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://selfoss.aditu.de"))
|
||||
startActivity(browserIntent)
|
||||
}, getString(R.string.intro_needs_selfoss_link)))
|
||||
|
||||
addSlide(SlideFragmentBuilder()
|
||||
.backgroundColor(R.color.colorPrimaryDark)
|
||||
.buttonsColor(R.color.colorAccentDark)
|
||||
.image(R.drawable.ic_thumb_up_white_48dp)
|
||||
.title(getString(R.string.intro_all_set_title))
|
||||
.description(getString(R.string.intro_all_set_message))
|
||||
.build())
|
||||
.backgroundColor(R.color.colorPrimaryDark)
|
||||
.buttonsColor(R.color.colorAccentDark)
|
||||
.image(R.drawable.ic_thumb_up_white_48dp)
|
||||
.title(getString(R.string.intro_all_set_title))
|
||||
.description(getString(R.string.intro_all_set_message))
|
||||
.build())
|
||||
}
|
||||
|
||||
override fun onFinish() {
|
||||
|
@ -18,11 +18,7 @@ import android.widget.Button
|
||||
import android.widget.EditText
|
||||
import android.widget.Switch
|
||||
import android.widget.TextView
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
|
||||
import apps.amine.bou.readerforselfoss.utils.Config
|
||||
import apps.amine.bou.readerforselfoss.utils.checkAndDisplayStoreApk
|
||||
import apps.amine.bou.readerforselfoss.utils.isUrlValid
|
||||
|
||||
import com.google.firebase.analytics.FirebaseAnalytics
|
||||
import com.mikepenz.aboutlibraries.Libs
|
||||
import com.mikepenz.aboutlibraries.LibsBuilder
|
||||
@ -30,21 +26,29 @@ import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
|
||||
import apps.amine.bou.readerforselfoss.utils.Config
|
||||
import apps.amine.bou.readerforselfoss.utils.checkAndDisplayStoreApk
|
||||
import apps.amine.bou.readerforselfoss.utils.isUrlValid
|
||||
|
||||
|
||||
|
||||
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 isWithLogin = 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
|
||||
|
||||
|
||||
|
||||
@ -52,35 +56,43 @@ class LoginActivity : AppCompatActivity() {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_login)
|
||||
|
||||
settings = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
|
||||
if (settings!!.getString("url", "").isNotEmpty()) {
|
||||
goToMain()
|
||||
} else {
|
||||
checkAndDisplayStoreApk(this@LoginActivity)
|
||||
if (intent.getBooleanExtra("baseUrlFail", false)) {
|
||||
val alertDialog = AlertDialog.Builder(this).create()
|
||||
alertDialog.setTitle(getString(R.string.warning_wrong_url))
|
||||
alertDialog.setMessage(getString(R.string.base_url_error))
|
||||
alertDialog.setButton(
|
||||
AlertDialog.BUTTON_NEUTRAL,
|
||||
"OK",
|
||||
{ dialog, _ -> dialog.dismiss() })
|
||||
alertDialog.show()
|
||||
}
|
||||
|
||||
isWithLogin = false
|
||||
isWithHTTPLogin = false
|
||||
inValidCount = 0
|
||||
|
||||
settings = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
|
||||
if (settings.getString("url", "").isNotEmpty()) {
|
||||
goToMain()
|
||||
} else {
|
||||
this@LoginActivity.checkAndDisplayStoreApk()
|
||||
}
|
||||
|
||||
mFirebaseAnalytics = FirebaseAnalytics.getInstance(this)
|
||||
mUrlView = findViewById(R.id.url) as EditText
|
||||
mLoginView = findViewById(R.id.login) as TextView
|
||||
mHTTPLoginView = findViewById(R.id.httpLogin) as TextView
|
||||
mPasswordView = findViewById(R.id.password) as EditText
|
||||
mHTTPPasswordView = findViewById(R.id.httpPassword) as EditText
|
||||
mUrlView = findViewById(R.id.url)
|
||||
mLoginView = findViewById(R.id.login)
|
||||
mHTTPLoginView = findViewById(R.id.httpLogin)
|
||||
mPasswordView = findViewById(R.id.password)
|
||||
mHTTPPasswordView = findViewById(R.id.httpPassword)
|
||||
mLoginFormView = findViewById(R.id.login_form)
|
||||
mProgressView = findViewById(R.id.login_progress)
|
||||
|
||||
val mSwitch = findViewById(R.id.withLogin) as Switch
|
||||
val mHTTPSwitch = findViewById(R.id.withHttpLogin) as Switch
|
||||
val mLoginLayout = findViewById(R.id.loginLayout) as TextInputLayout
|
||||
val mHTTPLoginLayout = findViewById(R.id.httpLoginInput) as TextInputLayout
|
||||
val mPasswordLayout = findViewById(R.id.passwordLayout) as TextInputLayout
|
||||
val mHTTPPasswordLayout = findViewById(R.id.httpPasswordInput) as TextInputLayout
|
||||
val mEmailSignInButton = findViewById(R.id.email_sign_in_button) as Button
|
||||
val mSwitch: Switch = findViewById(R.id.withLogin)
|
||||
val mHTTPSwitch: Switch = findViewById(R.id.withHttpLogin)
|
||||
val mLoginLayout: TextInputLayout = findViewById(R.id.loginLayout)
|
||||
val mHTTPLoginLayout: TextInputLayout = findViewById(R.id.httpLoginInput)
|
||||
val mPasswordLayout: TextInputLayout = findViewById(R.id.passwordLayout)
|
||||
val mHTTPPasswordLayout: TextInputLayout = findViewById(R.id.httpPasswordInput)
|
||||
val mEmailSignInButton: Button = findViewById(R.id.email_sign_in_button)
|
||||
|
||||
mPasswordView!!.setOnEditorActionListener(TextView.OnEditorActionListener { _, id, _ ->
|
||||
mPasswordView.setOnEditorActionListener(TextView.OnEditorActionListener { _, id, _ ->
|
||||
if (id == R.id.login || id == EditorInfo.IME_NULL) {
|
||||
attemptLogin()
|
||||
return@OnEditorActionListener true
|
||||
@ -92,26 +104,16 @@ class LoginActivity : AppCompatActivity() {
|
||||
|
||||
mSwitch.setOnCheckedChangeListener { _, b ->
|
||||
isWithLogin = !isWithLogin
|
||||
val visi: Int
|
||||
if (b) {
|
||||
visi = View.VISIBLE
|
||||
val visi: Int = if (b) View.VISIBLE else View.GONE
|
||||
|
||||
} else {
|
||||
visi = View.GONE
|
||||
}
|
||||
mLoginLayout.visibility = visi
|
||||
mPasswordLayout.visibility = visi
|
||||
}
|
||||
|
||||
mHTTPSwitch.setOnCheckedChangeListener { _, b ->
|
||||
isWithHTTPLogin = !isWithHTTPLogin
|
||||
val visi: Int
|
||||
if (b) {
|
||||
visi = View.VISIBLE
|
||||
val visi: Int = if (b) View.VISIBLE else View.GONE
|
||||
|
||||
} else {
|
||||
visi = View.GONE
|
||||
}
|
||||
mHTTPLoginLayout.visibility = visi
|
||||
mHTTPPasswordLayout.visibility = visi
|
||||
}
|
||||
@ -126,24 +128,24 @@ class LoginActivity : AppCompatActivity() {
|
||||
private fun attemptLogin() {
|
||||
|
||||
// Reset errors.
|
||||
mUrlView!!.error = null
|
||||
mLoginView!!.error = null
|
||||
mHTTPLoginView!!.error = null
|
||||
mPasswordView!!.error = null
|
||||
mHTTPPasswordView!!.error = null
|
||||
mUrlView.error = null
|
||||
mLoginView.error = null
|
||||
mHTTPLoginView.error = null
|
||||
mPasswordView.error = null
|
||||
mHTTPPasswordView.error = null
|
||||
|
||||
// Store values at the time of the login attempt.
|
||||
val url = mUrlView!!.text.toString()
|
||||
val login = mLoginView!!.text.toString()
|
||||
val httpLogin = mHTTPLoginView!!.text.toString()
|
||||
val password = mPasswordView!!.text.toString()
|
||||
val httpPassword = mHTTPPasswordView!!.text.toString()
|
||||
val url = mUrlView.text.toString()
|
||||
val login = mLoginView.text.toString()
|
||||
val httpLogin = mHTTPLoginView.text.toString()
|
||||
val password = mPasswordView.text.toString()
|
||||
val httpPassword = mHTTPPasswordView.text.toString()
|
||||
|
||||
var cancel = false
|
||||
var focusView: View? = null
|
||||
|
||||
if (!isUrlValid(url)) {
|
||||
mUrlView!!.error = getString(R.string.login_url_problem)
|
||||
if (!url.isUrlValid()) {
|
||||
mUrlView.error = getString(R.string.login_url_problem)
|
||||
focusView = mUrlView
|
||||
cancel = true
|
||||
inValidCount++
|
||||
@ -151,8 +153,10 @@ class LoginActivity : AppCompatActivity() {
|
||||
val alertDialog = AlertDialog.Builder(this).create()
|
||||
alertDialog.setTitle(getString(R.string.warning_wrong_url))
|
||||
alertDialog.setMessage(getString(R.string.text_wrong_url))
|
||||
alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "OK",
|
||||
{ dialog, _ -> dialog.dismiss() })
|
||||
alertDialog.setButton(
|
||||
AlertDialog.BUTTON_NEUTRAL,
|
||||
"OK",
|
||||
{ dialog, _ -> dialog.dismiss() })
|
||||
alertDialog.show()
|
||||
inValidCount = 0
|
||||
}
|
||||
@ -160,24 +164,24 @@ class LoginActivity : AppCompatActivity() {
|
||||
|
||||
if (isWithLogin || isWithHTTPLogin) {
|
||||
if (TextUtils.isEmpty(password)) {
|
||||
mPasswordView!!.error = getString(R.string.error_invalid_password)
|
||||
mPasswordView.error = getString(R.string.error_invalid_password)
|
||||
focusView = mPasswordView
|
||||
cancel = true
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(login)) {
|
||||
mLoginView!!.error = getString(R.string.error_field_required)
|
||||
mLoginView.error = getString(R.string.error_field_required)
|
||||
focusView = mLoginView
|
||||
cancel = true
|
||||
}
|
||||
}
|
||||
|
||||
if (cancel) {
|
||||
focusView!!.requestFocus()
|
||||
focusView?.requestFocus()
|
||||
} else {
|
||||
showProgress(true)
|
||||
|
||||
val editor = settings!!.edit()
|
||||
val editor = settings.edit()
|
||||
editor.putString("url", url)
|
||||
editor.putString("login", login)
|
||||
editor.putString("httpUserName", httpLogin)
|
||||
@ -185,7 +189,7 @@ class LoginActivity : AppCompatActivity() {
|
||||
editor.putString("httpPassword", httpPassword)
|
||||
editor.apply()
|
||||
|
||||
val api = SelfossApi(this@LoginActivity)
|
||||
val api = SelfossApi(this, this@LoginActivity)
|
||||
api.login().enqueue(object : Callback<SuccessResponse> {
|
||||
private fun preferenceError() {
|
||||
editor.remove("url")
|
||||
@ -194,17 +198,17 @@ class LoginActivity : AppCompatActivity() {
|
||||
editor.remove("password")
|
||||
editor.remove("httpPassword")
|
||||
editor.apply()
|
||||
mUrlView!!.error = getString(R.string.wrong_infos)
|
||||
mLoginView!!.error = getString(R.string.wrong_infos)
|
||||
mPasswordView!!.error = getString(R.string.wrong_infos)
|
||||
mHTTPLoginView!!.error = getString(R.string.wrong_infos)
|
||||
mHTTPPasswordView!!.error = getString(R.string.wrong_infos)
|
||||
mUrlView.error = getString(R.string.wrong_infos)
|
||||
mLoginView.error = getString(R.string.wrong_infos)
|
||||
mPasswordView.error = getString(R.string.wrong_infos)
|
||||
mHTTPLoginView.error = getString(R.string.wrong_infos)
|
||||
mHTTPPasswordView.error = getString(R.string.wrong_infos)
|
||||
showProgress(false)
|
||||
}
|
||||
|
||||
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {
|
||||
if (response.body() != null && response.body()!!.isSuccess) {
|
||||
mFirebaseAnalytics!!.logEvent(FirebaseAnalytics.Event.LOGIN, Bundle())
|
||||
mFirebaseAnalytics.logEvent(FirebaseAnalytics.Event.LOGIN, Bundle())
|
||||
goToMain()
|
||||
} else {
|
||||
preferenceError()
|
||||
@ -224,26 +228,33 @@ class LoginActivity : AppCompatActivity() {
|
||||
private fun showProgress(show: Boolean) {
|
||||
val shortAnimTime = resources.getInteger(android.R.integer.config_shortAnimTime)
|
||||
|
||||
mLoginFormView!!.visibility = if (show) View.GONE else View.VISIBLE
|
||||
mLoginFormView!!.animate().setDuration(shortAnimTime.toLong()).alpha(
|
||||
if (show) 0F else 1F).setListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
mLoginFormView!!.visibility = if (show) View.GONE else View.VISIBLE
|
||||
}
|
||||
})
|
||||
mLoginFormView.visibility = if (show) View.GONE else View.VISIBLE
|
||||
mLoginFormView
|
||||
.animate()
|
||||
.setDuration(shortAnimTime.toLong())
|
||||
.alpha(
|
||||
if (show) 0F else 1F
|
||||
).setListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
mLoginFormView.visibility = if (show) View.GONE else View.VISIBLE
|
||||
}
|
||||
})
|
||||
|
||||
mProgressView!!.visibility = if (show) View.VISIBLE else View.GONE
|
||||
mProgressView!!.animate().setDuration(shortAnimTime.toLong()).alpha(
|
||||
if (show) 1F else 0F).setListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
mProgressView!!.visibility = if (show) View.VISIBLE else View.GONE
|
||||
}
|
||||
})
|
||||
mProgressView.visibility = if (show) View.VISIBLE else View.GONE
|
||||
mProgressView
|
||||
.animate()
|
||||
.setDuration(shortAnimTime.toLong())
|
||||
.alpha(
|
||||
if (show) 1F else 0F
|
||||
).setListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
mProgressView.visibility = if (show) View.VISIBLE else View.GONE
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
val inflater = menuInflater
|
||||
inflater.inflate(R.menu.login_menu, menu)
|
||||
menuInflater.inflate(R.menu.login_menu, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
@ -251,10 +262,10 @@ class LoginActivity : AppCompatActivity() {
|
||||
when (item.itemId) {
|
||||
R.id.about -> {
|
||||
LibsBuilder()
|
||||
.withActivityStyle(Libs.ActivityStyle.LIGHT_DARK_TOOLBAR)
|
||||
.withAboutIconShown(true)
|
||||
.withAboutVersionShown(true)
|
||||
.start(this)
|
||||
.withActivityStyle(Libs.ActivityStyle.LIGHT_DARK_TOOLBAR)
|
||||
.withAboutIconShown(true)
|
||||
.withAboutVersionShown(true)
|
||||
.start(this)
|
||||
return true
|
||||
}
|
||||
else -> return super.onOptionsItemSelected(item)
|
||||
|
@ -6,6 +6,7 @@ import android.preference.PreferenceManager
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
|
||||
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
@ -1,9 +1,18 @@
|
||||
package apps.amine.bou.readerforselfoss
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
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.github.stkent.amplify.tracking.Amplify
|
||||
import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader
|
||||
import com.mikepenz.materialdrawer.util.DrawerImageLoader
|
||||
import io.fabric.sdk.android.Fabric
|
||||
import java.io.IOException
|
||||
|
||||
|
||||
class MyApp : MultiDexApplication() {
|
||||
@ -16,5 +25,25 @@ class MyApp : MultiDexApplication() {
|
||||
.setFeedbackEmailAddress(getString(R.string.feedback_email))
|
||||
.setAlwaysShow(BuildConfig.DEBUG)
|
||||
.applyAllDefaultRules()
|
||||
|
||||
try {
|
||||
Reservoir.init(this, 8192) //in bytes
|
||||
} catch (e: IOException) {
|
||||
//failure
|
||||
}
|
||||
|
||||
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 applicationContext.resources.getDrawable(R.mipmap.ic_launcher)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -9,10 +9,7 @@ import android.view.ViewGroup
|
||||
import android.widget.ImageButton
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import apps.amine.bou.readerforselfoss.api.mercury.MercuryApi
|
||||
import apps.amine.bou.readerforselfoss.api.mercury.ParsedContent
|
||||
import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent
|
||||
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
|
||||
|
||||
import com.bumptech.glide.Glide
|
||||
import org.sufficientlysecure.htmltextview.HtmlHttpImageGetter
|
||||
import org.sufficientlysecure.htmltextview.HtmlTextView
|
||||
@ -21,56 +18,67 @@ import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
import xyz.klinker.android.drag_dismiss.activity.DragDismissActivity
|
||||
|
||||
import apps.amine.bou.readerforselfoss.api.mercury.MercuryApi
|
||||
import apps.amine.bou.readerforselfoss.api.mercury.ParsedContent
|
||||
import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent
|
||||
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
|
||||
import apps.amine.bou.readerforselfoss.utils.shareLink
|
||||
|
||||
|
||||
class ReaderActivity : DragDismissActivity() {
|
||||
private var mCustomTabActivityHelper: CustomTabActivityHelper? = null
|
||||
private lateinit var mCustomTabActivityHelper: CustomTabActivityHelper
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
mCustomTabActivityHelper!!.bindCustomTabsService(this)
|
||||
mCustomTabActivityHelper.bindCustomTabsService(this)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
mCustomTabActivityHelper!!.unbindCustomTabsService(this)
|
||||
mCustomTabActivityHelper.unbindCustomTabsService(this)
|
||||
}
|
||||
|
||||
override fun onCreateContent(inflater: LayoutInflater, parent: ViewGroup, savedInstanceState: Bundle?): View {
|
||||
val v = inflater.inflate(R.layout.activity_reader, parent, false)
|
||||
showProgressBar()
|
||||
|
||||
val image = v.findViewById(R.id.imageView) as ImageView
|
||||
val source = v.findViewById(R.id.source) as TextView
|
||||
val title = v.findViewById(R.id.title) as TextView
|
||||
val content = v.findViewById(R.id.content) as HtmlTextView
|
||||
val image: ImageView = v.findViewById(R.id.imageView)
|
||||
val source: TextView = v.findViewById(R.id.source)
|
||||
val title: TextView = v.findViewById(R.id.title)
|
||||
val content: HtmlTextView = v.findViewById(R.id.content)
|
||||
val url = intent.getStringExtra("url")
|
||||
val parser = MercuryApi(getString(R.string.mercury))
|
||||
val browserBtn: ImageButton = v.findViewById(R.id.browserBtn) as ImageButton
|
||||
val shareBtn: ImageButton = v.findViewById(R.id.shareBtn) as ImageButton
|
||||
val browserBtn: ImageButton = v.findViewById(R.id.browserBtn)
|
||||
val shareBtn: ImageButton = v.findViewById(R.id.shareBtn)
|
||||
|
||||
|
||||
val customTabsIntent = buildCustomTabsIntent(this@ReaderActivity)
|
||||
val customTabsIntent = this@ReaderActivity.buildCustomTabsIntent()
|
||||
mCustomTabActivityHelper = CustomTabActivityHelper()
|
||||
mCustomTabActivityHelper!!.bindCustomTabsService(this)
|
||||
mCustomTabActivityHelper.bindCustomTabsService(this)
|
||||
|
||||
|
||||
parser.parseUrl(url).enqueue(object : Callback<ParsedContent> {
|
||||
override fun onResponse(call: Call<ParsedContent>, response: Response<ParsedContent>) {
|
||||
if (response.body() != null && response.body()!!.content.isNotEmpty()) {
|
||||
if (response.body() != null && response.body()!!.content != null && response.body()!!.content.isNotEmpty()) {
|
||||
source.text = response.body()!!.domain
|
||||
title.text = response.body()!!.title
|
||||
if (response.body()!!.content != null && !response.body()!!.content.isEmpty())
|
||||
content.setHtml(response.body()!!.content, HtmlHttpImageGetter(content, null, true))
|
||||
if (response.body()!!.content != null && !response.body()!!.content.isEmpty()) {
|
||||
try {
|
||||
content.setHtml(response.body()!!.content, HtmlHttpImageGetter(content, null, true))
|
||||
} catch (e: IndexOutOfBoundsException) {
|
||||
openInBrowserAfterFailing()
|
||||
}
|
||||
}
|
||||
if (response.body()!!.lead_image_url != null && !response.body()!!.lead_image_url.isEmpty())
|
||||
Glide.with(applicationContext).load(response.body()!!.lead_image_url).asBitmap().fitCenter().into(image)
|
||||
Glide
|
||||
.with(applicationContext)
|
||||
.load(response.body()!!.lead_image_url)
|
||||
.asBitmap()
|
||||
.fitCenter()
|
||||
.into(image)
|
||||
|
||||
shareBtn.setOnClickListener {
|
||||
val sendIntent = Intent()
|
||||
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))
|
||||
this@ReaderActivity.shareLink(response.body()!!.url)
|
||||
}
|
||||
|
||||
browserBtn.setOnClickListener {
|
||||
@ -81,16 +89,12 @@ class ReaderActivity : DragDismissActivity() {
|
||||
}
|
||||
|
||||
hideProgressBar()
|
||||
} else {
|
||||
errorAfterMercuryCall()
|
||||
}
|
||||
} else openInBrowserAfterFailing()
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<ParsedContent>, t: Throwable) {
|
||||
errorAfterMercuryCall()
|
||||
}
|
||||
override fun onFailure(call: Call<ParsedContent>, t: Throwable) = openInBrowserAfterFailing()
|
||||
|
||||
private fun errorAfterMercuryCall() {
|
||||
private fun openInBrowserAfterFailing() {
|
||||
CustomTabActivityHelper.openCustomTab(this@ReaderActivity, customTabsIntent, Uri.parse(url)
|
||||
) { _, uri ->
|
||||
val intent = Intent(Intent.ACTION_VIEW, uri)
|
||||
|
@ -6,14 +6,17 @@ import android.support.v7.app.AppCompatActivity
|
||||
import android.support.v7.widget.LinearLayoutManager
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.widget.Toast
|
||||
import apps.amine.bou.readerforselfoss.adapters.SourcesListAdapter
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.Sources
|
||||
|
||||
import com.melnykov.fab.FloatingActionButton
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
|
||||
import apps.amine.bou.readerforselfoss.adapters.SourcesListAdapter
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.Sources
|
||||
|
||||
|
||||
|
||||
class SourcesActivity : AppCompatActivity() {
|
||||
|
||||
@ -24,10 +27,10 @@ class SourcesActivity : AppCompatActivity() {
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
val mFab = findViewById(R.id.fab) as FloatingActionButton
|
||||
val mRecyclerView = findViewById(R.id.activity_sources) as RecyclerView
|
||||
val mFab: FloatingActionButton = findViewById(R.id.fab)
|
||||
val mRecyclerView: RecyclerView = findViewById(R.id.activity_sources)
|
||||
val mLayoutManager = LinearLayoutManager(this)
|
||||
val api = SelfossApi(this)
|
||||
val api = SelfossApi(this, this@SourcesActivity)
|
||||
var items: ArrayList<Sources> = ArrayList()
|
||||
|
||||
mFab.attachToRecyclerView(mRecyclerView)
|
||||
|
@ -1,12 +1,9 @@
|
||||
package apps.amine.bou.readerforselfoss.adapters
|
||||
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.net.Uri
|
||||
import android.support.constraint.ConstraintLayout
|
||||
import android.support.design.widget.Snackbar
|
||||
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory
|
||||
@ -20,13 +17,7 @@ import android.widget.ImageView
|
||||
import android.widget.ImageView.ScaleType
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import apps.amine.bou.readerforselfoss.R
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.Item
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
|
||||
import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent
|
||||
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
|
||||
import apps.amine.bou.readerforselfoss.utils.openItemUrl
|
||||
|
||||
import com.amulyakhare.textdrawable.TextDrawable
|
||||
import com.amulyakhare.textdrawable.util.ColorGenerator
|
||||
import com.bumptech.glide.Glide
|
||||
@ -40,10 +31,20 @@ import java.text.ParseException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
import apps.amine.bou.readerforselfoss.R
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.Item
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
|
||||
import apps.amine.bou.readerforselfoss.utils.*
|
||||
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
|
||||
|
||||
class ItemCardAdapter(private val app: Activity, private val items: ArrayList<Item>, private val api: SelfossApi,
|
||||
private val helper: CustomTabActivityHelper, private val internalBrowser: Boolean,
|
||||
private val articleViewer: Boolean, private val fullHeightCards: Boolean) : RecyclerView.Adapter<ItemCardAdapter.ViewHolder>() {
|
||||
class ItemCardAdapter(private val app: Activity,
|
||||
private val items: ArrayList<Item>,
|
||||
private val api: SelfossApi,
|
||||
private val helper: CustomTabActivityHelper,
|
||||
private val internalBrowser: Boolean,
|
||||
private val articleViewer: Boolean,
|
||||
private val fullHeightCards: Boolean) : RecyclerView.Adapter<ItemCardAdapter.ViewHolder>() {
|
||||
private val c: Context = app.applicationContext
|
||||
private val generator: ColorGenerator = ColorGenerator.MATERIAL
|
||||
|
||||
@ -56,60 +57,37 @@ class ItemCardAdapter(private val app: Activity, private val items: ArrayList<It
|
||||
val itm = items[position]
|
||||
|
||||
|
||||
holder.saveBtn!!.isLiked = itm.starred
|
||||
holder.title!!.text = Html.fromHtml(itm.title)
|
||||
holder.saveBtn.isLiked = itm.starred
|
||||
holder.title.text = Html.fromHtml(itm.title)
|
||||
|
||||
var sourceAndDate = itm.sourcetitle
|
||||
val d: Long
|
||||
try {
|
||||
d = SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(itm.datetime).time
|
||||
sourceAndDate += " " + DateUtils.getRelativeTimeSpanString(
|
||||
d,
|
||||
Date().time,
|
||||
DateUtils.MINUTE_IN_MILLIS,
|
||||
DateUtils.FORMAT_ABBREV_RELATIVE
|
||||
)
|
||||
} catch (e: ParseException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
holder.sourceTitleAndDate!!.text = sourceAndDate
|
||||
holder.sourceTitleAndDate.text = itm.sourceAndDateText()
|
||||
|
||||
if (itm.getThumbnail(c).isEmpty()) {
|
||||
Glide.clear(holder.itemImage)
|
||||
holder.itemImage!!.setImageDrawable(null)
|
||||
holder.itemImage.setImageDrawable(null)
|
||||
} else {
|
||||
if (fullHeightCards) {
|
||||
Glide.with(c).load(itm.getThumbnail(c)).asBitmap().fitCenter().into(holder.itemImage)
|
||||
c.bitmapFitCenter(itm.getThumbnail(c), holder.itemImage)
|
||||
} else {
|
||||
Glide.with(c).load(itm.getThumbnail(c)).asBitmap().centerCrop().into(holder.itemImage)
|
||||
c.bitmapCenterCrop(itm.getThumbnail(c), holder.itemImage)
|
||||
}
|
||||
}
|
||||
|
||||
val fHolder = holder
|
||||
if (itm.getIcon(c).isEmpty()) {
|
||||
val color = generator.getColor(itm.sourcetitle)
|
||||
val textDrawable = StringBuilder()
|
||||
for (s in itm.sourcetitle.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) {
|
||||
textDrawable.append(s[0])
|
||||
}
|
||||
|
||||
val builder = TextDrawable.builder().round()
|
||||
|
||||
val drawable = builder.build(textDrawable.toString(), color)
|
||||
holder.sourceImage!!.setImageDrawable(drawable)
|
||||
val drawable =
|
||||
TextDrawable
|
||||
.builder()
|
||||
.round()
|
||||
.build(itm.sourcetitle.toTextDrawableString(), color)
|
||||
holder.sourceImage.setImageDrawable(drawable)
|
||||
} else {
|
||||
|
||||
Glide.with(c).load(itm.getIcon(c)).asBitmap().centerCrop().into(object : BitmapImageViewTarget(holder.sourceImage) {
|
||||
override fun setResource(resource: Bitmap) {
|
||||
val circularBitmapDrawable = RoundedBitmapDrawableFactory.create(c.resources, resource)
|
||||
circularBitmapDrawable.isCircular = true
|
||||
fHolder.sourceImage!!.setImageDrawable(circularBitmapDrawable)
|
||||
}
|
||||
})
|
||||
c.circularBitmapDrawable(itm.getIcon(c), holder.sourceImage)
|
||||
}
|
||||
|
||||
holder.saveBtn!!.isLiked = itm.starred
|
||||
holder.saveBtn.isLiked = itm.starred
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
@ -135,7 +113,7 @@ class ItemCardAdapter(private val app: Activity, private val items: ArrayList<It
|
||||
}
|
||||
|
||||
val view = s.view
|
||||
val tv = view.findViewById(android.support.design.R.id.snackbar_text) as TextView
|
||||
val tv: TextView = view.findViewById(android.support.design.R.id.snackbar_text)
|
||||
tv.setTextColor(Color.WHITE)
|
||||
s.show()
|
||||
}
|
||||
@ -163,13 +141,13 @@ class ItemCardAdapter(private val app: Activity, private val items: ArrayList<It
|
||||
}
|
||||
|
||||
inner class ViewHolder(val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) {
|
||||
var saveBtn: LikeButton? = null
|
||||
var browserBtn: ImageButton? = null
|
||||
var shareBtn: ImageButton? = null
|
||||
var itemImage: ImageView? = null
|
||||
var sourceImage: ImageView? = null
|
||||
var title: TextView? = null
|
||||
var sourceTitleAndDate: TextView? = null
|
||||
lateinit var saveBtn: LikeButton
|
||||
lateinit var browserBtn: ImageButton
|
||||
lateinit var shareBtn: ImageButton
|
||||
lateinit var itemImage: ImageView
|
||||
lateinit var sourceImage: ImageView
|
||||
lateinit var title: TextView
|
||||
lateinit var sourceTitleAndDate: TextView
|
||||
|
||||
init {
|
||||
handleClickListeners()
|
||||
@ -177,27 +155,27 @@ class ItemCardAdapter(private val app: Activity, private val items: ArrayList<It
|
||||
}
|
||||
|
||||
private fun handleClickListeners() {
|
||||
sourceImage = mView.findViewById(R.id.sourceImage) as ImageView
|
||||
itemImage = mView.findViewById(R.id.itemImage) as ImageView
|
||||
title = mView.findViewById(R.id.title) as TextView
|
||||
sourceTitleAndDate = mView.findViewById(R.id.sourceTitleAndDate) as TextView
|
||||
saveBtn = mView.findViewById(R.id.favButton) as LikeButton
|
||||
shareBtn = mView.findViewById(R.id.shareBtn) as ImageButton
|
||||
browserBtn = mView.findViewById(R.id.browserBtn) as ImageButton
|
||||
sourceImage = mView.findViewById(R.id.sourceImage)
|
||||
itemImage = mView.findViewById(R.id.itemImage)
|
||||
title = mView.findViewById(R.id.title)
|
||||
sourceTitleAndDate = mView.findViewById(R.id.sourceTitleAndDate)
|
||||
saveBtn = mView.findViewById(R.id.favButton)
|
||||
shareBtn = mView.findViewById(R.id.shareBtn)
|
||||
browserBtn = mView.findViewById(R.id.browserBtn)
|
||||
|
||||
if (!fullHeightCards) {
|
||||
itemImage!!.maxHeight = c.resources.getDimension(R.dimen.card_image_max_height).toInt()
|
||||
itemImage!!.scaleType = ScaleType.CENTER_CROP
|
||||
itemImage.maxHeight = c.resources.getDimension(R.dimen.card_image_max_height).toInt()
|
||||
itemImage.scaleType = ScaleType.CENTER_CROP
|
||||
}
|
||||
|
||||
saveBtn!!.setOnLikeListener(object : OnLikeListener {
|
||||
saveBtn.setOnLikeListener(object : OnLikeListener {
|
||||
override fun liked(likeButton: LikeButton) {
|
||||
val (id) = items[adapterPosition]
|
||||
api.starrItem(id).enqueue(object : Callback<SuccessResponse> {
|
||||
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {}
|
||||
|
||||
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
|
||||
saveBtn!!.isLiked = false
|
||||
saveBtn.isLiked = false
|
||||
Toast.makeText(c, R.string.cant_mark_favortie, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
})
|
||||
@ -209,43 +187,32 @@ class ItemCardAdapter(private val app: Activity, private val items: ArrayList<It
|
||||
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {}
|
||||
|
||||
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
|
||||
saveBtn!!.isLiked = true
|
||||
saveBtn.isLiked = true
|
||||
Toast.makeText(c, R.string.cant_unmark_favortie, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
shareBtn!!.setOnClickListener {
|
||||
val i = items[adapterPosition]
|
||||
val sendIntent = Intent()
|
||||
sendIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
sendIntent.action = Intent.ACTION_SEND
|
||||
sendIntent.putExtra(Intent.EXTRA_TEXT, i.getLinkDecoded())
|
||||
sendIntent.type = "text/plain"
|
||||
c.startActivity(Intent.createChooser(sendIntent, c.getString(R.string.share)).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
|
||||
shareBtn.setOnClickListener {
|
||||
c.shareLink(items[adapterPosition].getLinkDecoded())
|
||||
}
|
||||
|
||||
browserBtn!!.setOnClickListener {
|
||||
val i = items[adapterPosition]
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
intent.data = Uri.parse(i.getLinkDecoded())
|
||||
c.startActivity(intent)
|
||||
browserBtn.setOnClickListener {
|
||||
c.openInBrowser(items[adapterPosition])
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCustomTabActions() {
|
||||
val customTabsIntent = buildCustomTabsIntent(c)
|
||||
val customTabsIntent = c.buildCustomTabsIntent()
|
||||
helper.bindCustomTabsService(app)
|
||||
|
||||
mView.setOnClickListener {
|
||||
openItemUrl(items[adapterPosition],
|
||||
customTabsIntent,
|
||||
internalBrowser,
|
||||
articleViewer,
|
||||
app,
|
||||
c)
|
||||
c.openItemUrl(items[adapterPosition],
|
||||
customTabsIntent,
|
||||
internalBrowser,
|
||||
articleViewer,
|
||||
app)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,13 +18,7 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.*
|
||||
import apps.amine.bou.readerforselfoss.R
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.Item
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
|
||||
import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent
|
||||
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
|
||||
import apps.amine.bou.readerforselfoss.utils.openItemUrl
|
||||
|
||||
import com.amulyakhare.textdrawable.TextDrawable
|
||||
import com.amulyakhare.textdrawable.util.ColorGenerator
|
||||
import com.bumptech.glide.Glide
|
||||
@ -38,10 +32,21 @@ import java.text.ParseException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
import apps.amine.bou.readerforselfoss.R
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.Item
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
|
||||
import apps.amine.bou.readerforselfoss.utils.*
|
||||
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
|
||||
|
||||
class ItemListAdapter(private val app: Activity, private val items: ArrayList<Item>, private val api: SelfossApi,
|
||||
private val helper: CustomTabActivityHelper, private val clickBehavior: Boolean,
|
||||
private val internalBrowser: Boolean, private val articleViewer: Boolean) : RecyclerView.Adapter<ItemListAdapter.ViewHolder>() {
|
||||
|
||||
class ItemListAdapter(private val app: Activity,
|
||||
private val items: ArrayList<Item>,
|
||||
private val api: SelfossApi,
|
||||
private val helper: CustomTabActivityHelper,
|
||||
private val clickBehavior: Boolean,
|
||||
private val internalBrowser: Boolean,
|
||||
private val articleViewer: Boolean) : RecyclerView.Adapter<ItemListAdapter.ViewHolder>() {
|
||||
private val generator: ColorGenerator = ColorGenerator.MATERIAL
|
||||
private val c: Context = app.applicationContext
|
||||
private val bars: ArrayList<Boolean> = ArrayList(Collections.nCopies(items.size + 1, false))
|
||||
@ -55,41 +60,27 @@ class ItemListAdapter(private val app: Activity, private val items: ArrayList<It
|
||||
val itm = items[position]
|
||||
|
||||
|
||||
holder.saveBtn!!.isLiked = itm.starred
|
||||
holder.title!!.text = Html.fromHtml(itm.title)
|
||||
holder.saveBtn.isLiked = itm.starred
|
||||
holder.title.text = Html.fromHtml(itm.title)
|
||||
|
||||
var sourceAndDate = itm.sourcetitle
|
||||
val d: Long
|
||||
try {
|
||||
d = SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(itm.datetime).time
|
||||
sourceAndDate += " " + DateUtils.getRelativeTimeSpanString(
|
||||
d,
|
||||
Date().time,
|
||||
DateUtils.MINUTE_IN_MILLIS,
|
||||
DateUtils.FORMAT_ABBREV_RELATIVE
|
||||
)
|
||||
} catch (e: ParseException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
holder.sourceTitleAndDate!!.text = sourceAndDate
|
||||
holder.sourceTitleAndDate.text = itm.sourceAndDateText()
|
||||
|
||||
if (itm.getThumbnail(c).isEmpty()) {
|
||||
val sizeInInt = 46
|
||||
val sizeInDp = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP, sizeInInt.toFloat(), c.resources
|
||||
.displayMetrics).toInt()
|
||||
TypedValue.COMPLEX_UNIT_DIP, sizeInInt.toFloat(), c.resources
|
||||
.displayMetrics).toInt()
|
||||
|
||||
val marginInInt = 16
|
||||
val marginInDp = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP, marginInInt.toFloat(), c.resources
|
||||
.displayMetrics).toInt()
|
||||
|
||||
val params = holder.sourceImage!!.layoutParams as ViewGroup.MarginLayoutParams
|
||||
val params = holder.sourceImage.layoutParams as ViewGroup.MarginLayoutParams
|
||||
params.height = sizeInDp
|
||||
params.width = sizeInDp
|
||||
params.setMargins(marginInDp, 0, 0, 0)
|
||||
holder.sourceImage!!.layoutParams = params
|
||||
holder.sourceImage.layoutParams = params
|
||||
|
||||
if (itm.getIcon(c).isEmpty()) {
|
||||
val color = generator.getColor(itm.sourcetitle)
|
||||
@ -101,34 +92,20 @@ class ItemListAdapter(private val app: Activity, private val items: ArrayList<It
|
||||
val builder = TextDrawable.builder().round()
|
||||
|
||||
val drawable = builder.build(textDrawable.toString(), color)
|
||||
holder.sourceImage!!.setImageDrawable(drawable)
|
||||
holder.sourceImage.setImageDrawable(drawable)
|
||||
} else {
|
||||
|
||||
val fHolder = holder
|
||||
Glide.with(c).load(itm.getIcon(c)).asBitmap().centerCrop().into(object : BitmapImageViewTarget(holder.sourceImage) {
|
||||
override fun setResource(resource: Bitmap) {
|
||||
val circularBitmapDrawable = RoundedBitmapDrawableFactory.create(c.resources, resource)
|
||||
circularBitmapDrawable.isCircular = true
|
||||
fHolder.sourceImage!!.setImageDrawable(circularBitmapDrawable)
|
||||
}
|
||||
})
|
||||
c.circularBitmapDrawable(itm.getIcon(c), holder.sourceImage)
|
||||
}
|
||||
} else {
|
||||
Glide.with(c).load(itm.getThumbnail(c)).asBitmap().centerCrop().into(holder.sourceImage)
|
||||
c.bitmapCenterCrop(itm.getThumbnail(c), holder.sourceImage)
|
||||
}
|
||||
|
||||
if (bars[position]) {
|
||||
holder.actionBar!!.visibility = View.VISIBLE
|
||||
} else {
|
||||
holder.actionBar!!.visibility = View.GONE
|
||||
}
|
||||
if (bars[position]) holder.actionBar.visibility = View.VISIBLE else holder.actionBar.visibility = View.GONE
|
||||
|
||||
holder.saveBtn!!.isLiked = itm.starred
|
||||
holder.saveBtn.isLiked = itm.starred
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return items.size
|
||||
}
|
||||
override fun getItemCount(): Int = items.size
|
||||
|
||||
|
||||
private fun doUnmark(i: Item, position: Int) {
|
||||
@ -150,7 +127,7 @@ class ItemListAdapter(private val app: Activity, private val items: ArrayList<It
|
||||
}
|
||||
|
||||
val view = s.view
|
||||
val tv = view.findViewById(android.support.design.R.id.snackbar_text) as TextView
|
||||
val tv: TextView = view.findViewById(android.support.design.R.id.snackbar_text)
|
||||
tv.setTextColor(Color.WHITE)
|
||||
s.show()
|
||||
}
|
||||
@ -178,13 +155,13 @@ class ItemListAdapter(private val app: Activity, private val items: ArrayList<It
|
||||
}
|
||||
|
||||
inner class ViewHolder(val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) {
|
||||
var saveBtn: LikeButton? = null
|
||||
var browserBtn: ImageButton? = null
|
||||
var shareBtn: ImageButton? = null
|
||||
var actionBar: RelativeLayout? = null
|
||||
var sourceImage: ImageView? = null
|
||||
var title: TextView? = null
|
||||
var sourceTitleAndDate: TextView? = null
|
||||
lateinit var saveBtn: LikeButton
|
||||
lateinit var browserBtn: ImageButton
|
||||
lateinit var shareBtn: ImageButton
|
||||
lateinit var actionBar: RelativeLayout
|
||||
lateinit var sourceImage: ImageView
|
||||
lateinit var title: TextView
|
||||
lateinit var sourceTitleAndDate: TextView
|
||||
|
||||
init {
|
||||
handleClickListeners()
|
||||
@ -192,23 +169,23 @@ class ItemListAdapter(private val app: Activity, private val items: ArrayList<It
|
||||
}
|
||||
|
||||
private fun handleClickListeners() {
|
||||
actionBar = mView.findViewById(R.id.actionBar) as RelativeLayout
|
||||
sourceImage = mView.findViewById(R.id.itemImage) as ImageView
|
||||
title = mView.findViewById(R.id.title) as TextView
|
||||
sourceTitleAndDate = mView.findViewById(R.id.sourceTitleAndDate) as TextView
|
||||
saveBtn = mView.findViewById(R.id.favButton) as LikeButton
|
||||
shareBtn = mView.findViewById(R.id.shareBtn) as ImageButton
|
||||
browserBtn = mView.findViewById(R.id.browserBtn) as ImageButton
|
||||
actionBar = mView.findViewById(R.id.actionBar)
|
||||
sourceImage = mView.findViewById(R.id.itemImage)
|
||||
title = mView.findViewById(R.id.title)
|
||||
sourceTitleAndDate = mView.findViewById(R.id.sourceTitleAndDate)
|
||||
saveBtn = mView.findViewById(R.id.favButton)
|
||||
shareBtn = mView.findViewById(R.id.shareBtn)
|
||||
browserBtn = mView.findViewById(R.id.browserBtn)
|
||||
|
||||
|
||||
saveBtn!!.setOnLikeListener(object : OnLikeListener {
|
||||
saveBtn.setOnLikeListener(object : OnLikeListener {
|
||||
override fun liked(likeButton: LikeButton) {
|
||||
val (id) = items[adapterPosition]
|
||||
api.starrItem(id).enqueue(object : Callback<SuccessResponse> {
|
||||
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {}
|
||||
|
||||
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
|
||||
saveBtn!!.isLiked = false
|
||||
saveBtn.isLiked = false
|
||||
Toast.makeText(c, R.string.cant_mark_favortie, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
})
|
||||
@ -220,46 +197,36 @@ class ItemListAdapter(private val app: Activity, private val items: ArrayList<It
|
||||
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {}
|
||||
|
||||
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
|
||||
saveBtn!!.isLiked = true
|
||||
saveBtn.isLiked = true
|
||||
Toast.makeText(c, R.string.cant_unmark_favortie, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
shareBtn!!.setOnClickListener {
|
||||
val i = items[adapterPosition]
|
||||
val sendIntent = Intent()
|
||||
sendIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
sendIntent.action = Intent.ACTION_SEND
|
||||
sendIntent.putExtra(Intent.EXTRA_TEXT, i.getLinkDecoded())
|
||||
sendIntent.type = "text/plain"
|
||||
c.startActivity(Intent.createChooser(sendIntent, c.getString(R.string.share)).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
|
||||
shareBtn.setOnClickListener {
|
||||
c.shareLink(items[adapterPosition].getLinkDecoded())
|
||||
}
|
||||
|
||||
browserBtn!!.setOnClickListener {
|
||||
val i = items[adapterPosition]
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
intent.data = Uri.parse(i.getLinkDecoded())
|
||||
c.startActivity(intent)
|
||||
browserBtn.setOnClickListener {
|
||||
c.openInBrowser(items[adapterPosition])
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun handleCustomTabActions() {
|
||||
val customTabsIntent = buildCustomTabsIntent(c)
|
||||
val customTabsIntent = c.buildCustomTabsIntent()
|
||||
helper.bindCustomTabsService(app)
|
||||
|
||||
|
||||
if (!clickBehavior) {
|
||||
mView.setOnClickListener {
|
||||
openItemUrl(items[adapterPosition],
|
||||
customTabsIntent,
|
||||
internalBrowser,
|
||||
articleViewer,
|
||||
app,
|
||||
c)
|
||||
c.openItemUrl(items[adapterPosition],
|
||||
customTabsIntent,
|
||||
internalBrowser,
|
||||
articleViewer,
|
||||
app)
|
||||
}
|
||||
mView.setOnLongClickListener {
|
||||
actionBarShowHide()
|
||||
@ -268,12 +235,11 @@ class ItemListAdapter(private val app: Activity, private val items: ArrayList<It
|
||||
} else {
|
||||
mView.setOnClickListener { actionBarShowHide() }
|
||||
mView.setOnLongClickListener {
|
||||
openItemUrl(items[adapterPosition],
|
||||
customTabsIntent,
|
||||
internalBrowser,
|
||||
articleViewer,
|
||||
app,
|
||||
c)
|
||||
c.openItemUrl(items[adapterPosition],
|
||||
customTabsIntent,
|
||||
internalBrowser,
|
||||
articleViewer,
|
||||
app)
|
||||
true
|
||||
}
|
||||
}
|
||||
@ -281,10 +247,7 @@ class ItemListAdapter(private val app: Activity, private val items: ArrayList<It
|
||||
|
||||
private fun actionBarShowHide() {
|
||||
bars[adapterPosition] = true
|
||||
if (actionBar!!.visibility == View.GONE)
|
||||
actionBar!!.visibility = View.VISIBLE
|
||||
else
|
||||
actionBar!!.visibility = View.GONE
|
||||
if (actionBar.visibility == View.GONE) actionBar.visibility = View.VISIBLE else actionBar.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,9 +2,7 @@ package apps.amine.bou.readerforselfoss.adapters
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.support.constraint.ConstraintLayout
|
||||
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
@ -12,19 +10,24 @@ import android.widget.Button
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import apps.amine.bou.readerforselfoss.R
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.Sources
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
|
||||
|
||||
import com.amulyakhare.textdrawable.TextDrawable
|
||||
import com.amulyakhare.textdrawable.util.ColorGenerator
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.target.BitmapImageViewTarget
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
|
||||
class SourcesListAdapter(private val app: Activity, private val items: ArrayList<Sources>, private val api: SelfossApi) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>() {
|
||||
import apps.amine.bou.readerforselfoss.R
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.Sources
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
|
||||
import apps.amine.bou.readerforselfoss.utils.circularBitmapDrawable
|
||||
import apps.amine.bou.readerforselfoss.utils.toTextDrawableString
|
||||
|
||||
|
||||
class SourcesListAdapter(private val app: Activity,
|
||||
private val items: ArrayList<Sources>,
|
||||
private val api: SelfossApi) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>() {
|
||||
private val c: Context = app.baseContext
|
||||
private val generator: ColorGenerator = ColorGenerator.MATERIAL
|
||||
|
||||
@ -36,29 +39,20 @@ class SourcesListAdapter(private val app: Activity, private val items: ArrayList
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val itm = items[position]
|
||||
|
||||
val fHolder = holder
|
||||
if (itm.getIcon(c).isEmpty()) {
|
||||
val color = generator.getColor(itm.title)
|
||||
val textDrawable = StringBuilder()
|
||||
for (s in itm.title.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) {
|
||||
textDrawable.append(s[0])
|
||||
}
|
||||
|
||||
val builder = TextDrawable.builder().round()
|
||||
|
||||
val drawable = builder.build(textDrawable.toString(), color)
|
||||
holder.sourceImage!!.setImageDrawable(drawable)
|
||||
val drawable =
|
||||
TextDrawable
|
||||
.builder()
|
||||
.round()
|
||||
.build(itm.title.toTextDrawableString(), color)
|
||||
holder.sourceImage.setImageDrawable(drawable)
|
||||
} else {
|
||||
Glide.with(c).load(itm.getIcon(c)).asBitmap().centerCrop().into(object : BitmapImageViewTarget(holder.sourceImage) {
|
||||
override fun setResource(resource: Bitmap) {
|
||||
val circularBitmapDrawable = RoundedBitmapDrawableFactory.create(c.resources, resource)
|
||||
circularBitmapDrawable.isCircular = true
|
||||
fHolder.sourceImage!!.setImageDrawable(circularBitmapDrawable)
|
||||
}
|
||||
})
|
||||
c.circularBitmapDrawable(itm.getIcon(c), holder.sourceImage)
|
||||
}
|
||||
|
||||
holder.sourceTitle!!.text = itm.title
|
||||
holder.sourceTitle.text = itm.title
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
@ -66,8 +60,8 @@ class SourcesListAdapter(private val app: Activity, private val items: ArrayList
|
||||
}
|
||||
|
||||
inner class ViewHolder(internal val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) {
|
||||
var sourceImage: ImageView? = null
|
||||
var sourceTitle: TextView? = null
|
||||
lateinit var sourceImage: ImageView
|
||||
lateinit var sourceTitle: TextView
|
||||
|
||||
init {
|
||||
|
||||
@ -75,10 +69,10 @@ class SourcesListAdapter(private val app: Activity, private val items: ArrayList
|
||||
}
|
||||
|
||||
private fun handleClickListeners() {
|
||||
sourceImage = mView.findViewById(R.id.itemImage) as ImageView
|
||||
sourceTitle = mView.findViewById(R.id.sourceTitle) as TextView
|
||||
sourceImage = mView.findViewById(R.id.itemImage)
|
||||
sourceTitle = mView.findViewById(R.id.sourceTitle)
|
||||
|
||||
val deleteBtn = mView.findViewById(R.id.deleteBtn) as Button
|
||||
val deleteBtn: Button = mView.findViewById(R.id.deleteBtn)
|
||||
|
||||
deleteBtn.setOnClickListener {
|
||||
val (id) = items[adapterPosition]
|
||||
@ -89,12 +83,12 @@ class SourcesListAdapter(private val app: Activity, private val items: ArrayList
|
||||
notifyItemRemoved(adapterPosition)
|
||||
notifyItemRangeChanged(adapterPosition, itemCount)
|
||||
} else {
|
||||
Toast.makeText(app, "Petit soucis lors de la suppression de la source.", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(app, R.string.can_delete_source, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
|
||||
Toast.makeText(app, "Petit soucis lors de la suppression de la source.", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(app, R.string.can_delete_source, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package apps.amine.bou.readerforselfoss.api.mercury
|
||||
|
||||
|
||||
import com.google.gson.GsonBuilder
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
@ -9,6 +8,7 @@ import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
|
||||
|
||||
|
||||
class MercuryApi(private val key: String) {
|
||||
private val service: MercuryService
|
||||
|
||||
@ -21,8 +21,13 @@ class MercuryApi(private val key: String) {
|
||||
val gson = GsonBuilder()
|
||||
.setLenient()
|
||||
.create()
|
||||
val retrofit = Retrofit.Builder().baseUrl("https://mercury.postlight.com").client(client)
|
||||
.addConverterFactory(GsonConverterFactory.create(gson)).build()
|
||||
val retrofit =
|
||||
Retrofit
|
||||
.Builder()
|
||||
.baseUrl("https://mercury.postlight.com")
|
||||
.client(client)
|
||||
.addConverterFactory(GsonConverterFactory.create(gson))
|
||||
.build()
|
||||
service = retrofit.create(MercuryService::class.java)
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
|
||||
|
||||
|
||||
class ParsedContent(val title: String,
|
||||
val content: String,
|
||||
val date_published: String,
|
||||
@ -24,17 +25,17 @@ class ParsedContent(val title: String,
|
||||
}
|
||||
|
||||
constructor(source: Parcel) : this(
|
||||
title = source.readString(),
|
||||
content = source.readString(),
|
||||
date_published = source.readString(),
|
||||
lead_image_url = source.readString(),
|
||||
dek = source.readString(),
|
||||
url = source.readString(),
|
||||
domain = source.readString(),
|
||||
excerpt = source.readString(),
|
||||
total_pages = source.readInt(),
|
||||
rendered_pages = source.readInt(),
|
||||
next_page_url = source.readString()
|
||||
title = source.readString(),
|
||||
content = source.readString(),
|
||||
date_published = source.readString(),
|
||||
lead_image_url = source.readString(),
|
||||
dek = source.readString(),
|
||||
url = source.readString(),
|
||||
domain = source.readString(),
|
||||
excerpt = source.readString(),
|
||||
total_pages = source.readInt(),
|
||||
rendered_pages = source.readInt(),
|
||||
next_page_url = source.readString()
|
||||
)
|
||||
|
||||
override fun describeContents() = 0
|
||||
|
@ -7,6 +7,7 @@ import retrofit2.http.Header
|
||||
import retrofit2.http.Query
|
||||
|
||||
|
||||
|
||||
interface MercuryService {
|
||||
@GET("parser")
|
||||
fun parseUrl(@Query("url") url: String, @Header("x-api-key") key: String): Call<ParsedContent>
|
||||
|
@ -1,10 +1,12 @@
|
||||
package apps.amine.bou.readerforselfoss.api.selfoss
|
||||
|
||||
import java.lang.reflect.Type
|
||||
|
||||
import com.google.gson.JsonParseException
|
||||
import com.google.gson.JsonDeserializationContext
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonDeserializer
|
||||
import java.lang.reflect.Type
|
||||
|
||||
|
||||
|
||||
internal class BooleanTypeAdapter : JsonDeserializer<Boolean> {
|
||||
|
@ -1,8 +1,14 @@
|
||||
package apps.amine.bou.readerforselfoss.api.selfoss
|
||||
|
||||
|
||||
import android.app.Activity
|
||||
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.CachingAuthenticatorDecorator
|
||||
import com.burgstaller.okhttp.DispatchingAuthenticator
|
||||
@ -12,99 +18,97 @@ import com.burgstaller.okhttp.digest.Credentials
|
||||
import com.burgstaller.okhttp.digest.DigestAuthenticator
|
||||
import com.google.gson.GsonBuilder
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Call
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
import apps.amine.bou.readerforselfoss.utils.Config
|
||||
|
||||
|
||||
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 userName: String
|
||||
private val password: String
|
||||
|
||||
init {
|
||||
fun Credentials.createAuthenticator(): DispatchingAuthenticator =
|
||||
DispatchingAuthenticator.Builder()
|
||||
.with("digest", DigestAuthenticator(this))
|
||||
.with("basic", BasicAuthenticator(this))
|
||||
.build()
|
||||
|
||||
val interceptor = HttpLoggingInterceptor()
|
||||
interceptor.level = HttpLoggingInterceptor.Level.BODY
|
||||
|
||||
val httpBuilder = OkHttpClient.Builder()
|
||||
fun DispatchingAuthenticator.getHttpClien(): OkHttpClient {
|
||||
val authCache = ConcurrentHashMap<String, CachingAuthenticator>()
|
||||
|
||||
val httpUserName = config.httpUserLogin
|
||||
val httpPassword = config.httpUserPassword
|
||||
|
||||
val credentials = Credentials(httpUserName, httpPassword)
|
||||
val basicAuthenticator = BasicAuthenticator(credentials)
|
||||
val digestAuthenticator = DigestAuthenticator(credentials)
|
||||
|
||||
// note that all auth schemes should be registered as lowercase!
|
||||
val authenticator = DispatchingAuthenticator.Builder()
|
||||
.with("digest", digestAuthenticator)
|
||||
.with("basic", basicAuthenticator)
|
||||
.build()
|
||||
|
||||
val client = httpBuilder
|
||||
.authenticator(CachingAuthenticatorDecorator(authenticator, authCache))
|
||||
.addInterceptor(AuthenticationCacheInterceptor(authCache))
|
||||
.addInterceptor(interceptor)
|
||||
.build()
|
||||
return OkHttpClient
|
||||
.Builder()
|
||||
.authenticator(CachingAuthenticatorDecorator(this, authCache))
|
||||
.addInterceptor(AuthenticationCacheInterceptor(authCache))
|
||||
.build()
|
||||
}
|
||||
|
||||
|
||||
val builder = GsonBuilder()
|
||||
builder.registerTypeAdapter(Boolean::class.javaPrimitiveType, BooleanTypeAdapter())
|
||||
init {
|
||||
userName = config.userLogin
|
||||
password = config.userPassword
|
||||
|
||||
val gson = builder
|
||||
val authenticator =
|
||||
Credentials(
|
||||
config.httpUserLogin,
|
||||
config.httpUserPassword
|
||||
).createAuthenticator()
|
||||
|
||||
val gson =
|
||||
GsonBuilder()
|
||||
.registerTypeAdapter(Boolean::class.javaPrimitiveType, BooleanTypeAdapter())
|
||||
.setLenient()
|
||||
.create()
|
||||
|
||||
userName = config.userLogin
|
||||
password = config.userPassword
|
||||
val retrofit = Retrofit.Builder().baseUrl(config.baseUrl).client(client)
|
||||
.addConverterFactory(GsonConverterFactory.create(gson)).build()
|
||||
service = retrofit.create(SelfossService::class.java)
|
||||
|
||||
try {
|
||||
val retrofit =
|
||||
Retrofit
|
||||
.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> {
|
||||
return service.loginToSelfoss(config.userLogin, config.userPassword)
|
||||
}
|
||||
fun login(): Call<SuccessResponse> =
|
||||
service.loginToSelfoss(config.userLogin, config.userPassword)
|
||||
|
||||
val readItems: Call<List<Item>>
|
||||
get() = getItems("read")
|
||||
fun readItems(tag: String?, sourceId: Long?, search: String?): Call<List<Item>> =
|
||||
getItems("read", tag, sourceId, search)
|
||||
|
||||
val unreadItems: Call<List<Item>>
|
||||
get() = getItems("unread")
|
||||
fun newItems(tag: String?, sourceId: Long?, search: String?): Call<List<Item>> =
|
||||
getItems("unread", tag, sourceId, search)
|
||||
|
||||
val starredItems: Call<List<Item>>
|
||||
get() = getItems("starred")
|
||||
fun starredItems(tag: String?, sourceId: Long?, search: String?): Call<List<Item>> =
|
||||
getItems("starred", tag, sourceId, search)
|
||||
|
||||
private fun getItems(type: String): Call<List<Item>> {
|
||||
return service.getItems(type, userName, password)
|
||||
}
|
||||
private fun getItems(type: String, tag: String?, sourceId: Long?, search: String?): Call<List<Item>> =
|
||||
service.getItems(type, tag, sourceId, search, userName, password)
|
||||
|
||||
fun markItem(itemId: String): Call<SuccessResponse> {
|
||||
return service.markAsRead(itemId, userName, password)
|
||||
}
|
||||
fun markItem(itemId: String): Call<SuccessResponse> =
|
||||
service.markAsRead(itemId, userName, password)
|
||||
|
||||
fun unmarkItem(itemId: String): Call<SuccessResponse> {
|
||||
return service.unmarkAsRead(itemId, userName, password)
|
||||
}
|
||||
fun unmarkItem(itemId: String): Call<SuccessResponse> =
|
||||
service.unmarkAsRead(itemId, userName, password)
|
||||
|
||||
fun readAll(ids: List<String>): Call<SuccessResponse> {
|
||||
return service.markAllAsRead(ids, userName, password)
|
||||
}
|
||||
fun readAll(ids: List<String>): Call<SuccessResponse> =
|
||||
service.markAllAsRead(ids, userName, password)
|
||||
|
||||
fun starrItem(itemId: String): Call<SuccessResponse> {
|
||||
return service.starr(itemId, userName, password)
|
||||
}
|
||||
fun starrItem(itemId: String): Call<SuccessResponse> =
|
||||
service.starr(itemId, userName, password)
|
||||
|
||||
|
||||
fun unstarrItem(itemId: String): Call<SuccessResponse> {
|
||||
return service.unstarr(itemId, userName, password)
|
||||
}
|
||||
fun unstarrItem(itemId: String): Call<SuccessResponse> =
|
||||
service.unstarr(itemId, userName, password)
|
||||
|
||||
val stats: Call<Stats>
|
||||
get() = service.stats(userName, password)
|
||||
@ -112,23 +116,21 @@ class SelfossApi(c: Context) {
|
||||
val tags: Call<List<Tag>>
|
||||
get() = service.tags(userName, password)
|
||||
|
||||
fun update(): Call<String> {
|
||||
return service.update(userName, password)
|
||||
}
|
||||
fun update(): Call<String> =
|
||||
service.update(userName, password)
|
||||
|
||||
val sources: Call<List<Sources>>
|
||||
get() = service.sources(userName, password)
|
||||
|
||||
fun deleteSource(id: String): Call<SuccessResponse> {
|
||||
return service.deleteSource(id, userName, password)
|
||||
}
|
||||
fun deleteSource(id: String): Call<SuccessResponse> =
|
||||
service.deleteSource(id, userName, password)
|
||||
|
||||
fun spouts(): Call<Map<String, Spout>> {
|
||||
return service.spouts(userName, password)
|
||||
}
|
||||
fun spouts(): Call<Map<String, Spout>> =
|
||||
service.spouts(userName, password)
|
||||
|
||||
fun createSource(title: String, url: String, spout: String, tags: String, filter: String): Call<SuccessResponse> {
|
||||
return service.createSource(title, url, spout, tags, filter, userName, password)
|
||||
}
|
||||
fun createSource(title: String, url: String, spout: String, tags: String, filter: String): Call<SuccessResponse> =
|
||||
service.createSource(title, url, spout, tags, filter, userName, password)
|
||||
|
||||
}
|
||||
|
||||
// codebeat:enable[ARITY,TOO_MANY_FUNCTIONS]
|
@ -4,15 +4,17 @@ import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
|
||||
import apps.amine.bou.readerforselfoss.utils.Config
|
||||
import apps.amine.bou.readerforselfoss.utils.isEmptyOrNullOrNullString
|
||||
|
||||
|
||||
|
||||
private fun constructUrl(config: Config?, path: String, file: String): String {
|
||||
val baseUriBuilder = Uri.parse(config!!.baseUrl).buildUpon()
|
||||
baseUriBuilder.appendPath(path).appendPath(file)
|
||||
|
||||
return if (isEmptyOrNullOrNullString(file)) ""
|
||||
return if (file.isEmptyOrNullOrNullString()) ""
|
||||
else baseUriBuilder.toString()
|
||||
}
|
||||
|
||||
@ -65,15 +67,15 @@ data class Item(val id: String,
|
||||
}
|
||||
|
||||
constructor(source: Parcel) : this(
|
||||
id = source.readString(),
|
||||
datetime = source.readString(),
|
||||
title = source.readString(),
|
||||
unread = 0.toByte() != source.readByte(),
|
||||
starred = 0.toByte() != source.readByte(),
|
||||
thumbnail = source.readString(),
|
||||
icon = source.readString(),
|
||||
link = source.readString(),
|
||||
sourcetitle = source.readString()
|
||||
id = source.readString(),
|
||||
datetime = source.readString(),
|
||||
title = source.readString(),
|
||||
unread = 0.toByte() != source.readByte(),
|
||||
starred = 0.toByte() != source.readByte(),
|
||||
thumbnail = source.readString(),
|
||||
icon = source.readString(),
|
||||
link = source.readString(),
|
||||
sourcetitle = source.readString()
|
||||
)
|
||||
|
||||
override fun describeContents() = 0
|
||||
|
@ -1,6 +1,5 @@
|
||||
package apps.amine.bou.readerforselfoss.api.selfoss
|
||||
|
||||
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.DELETE
|
||||
import retrofit2.http.Field
|
||||
@ -11,55 +10,86 @@ import retrofit2.http.Path
|
||||
import retrofit2.http.Query
|
||||
|
||||
|
||||
|
||||
// codebeat:disable[ARITY]
|
||||
internal interface SelfossService {
|
||||
@GET("login")
|
||||
fun loginToSelfoss(@Query("username") username: String, @Query("password") password: String): Call<SuccessResponse>
|
||||
|
||||
@GET("items")
|
||||
fun getItems(@Query("type") type: String, @Query("username") username: String, @Query("password") password: String): Call<List<Item>>
|
||||
fun getItems(@Query("type") type: String,
|
||||
@Query("tag") tag: String?,
|
||||
@Query("source") source: Long?,
|
||||
@Query("search") search: String?,
|
||||
@Query("username") username: String,
|
||||
@Query("password") password: String): Call<List<Item>>
|
||||
|
||||
@POST("mark/{id}")
|
||||
fun markAsRead(@Path("id") id: String, @Query("username") username: String, @Query("password") password: String): Call<SuccessResponse>
|
||||
fun markAsRead(@Path("id") id: String,
|
||||
@Query("username") username: String,
|
||||
@Query("password") password: String): Call<SuccessResponse>
|
||||
|
||||
|
||||
@POST("unmark/{id}")
|
||||
fun unmarkAsRead(@Path("id") id: String, @Query("username") username: String, @Query("password") password: String): Call<SuccessResponse>
|
||||
fun unmarkAsRead(@Path("id") id: String,
|
||||
@Query("username") username: String,
|
||||
@Query("password") password: String): Call<SuccessResponse>
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST("mark")
|
||||
fun markAllAsRead(@Field("ids[]") ids: List<String>, @Query("username") username: String, @Query("password") password: String): Call<SuccessResponse>
|
||||
fun markAllAsRead(@Field("ids[]") ids: List<String>,
|
||||
@Query("username") username: String,
|
||||
@Query("password") password: String): Call<SuccessResponse>
|
||||
|
||||
|
||||
@POST("starr/{id}")
|
||||
fun starr(@Path("id") id: String, @Query("username") username: String, @Query("password") password: String): Call<SuccessResponse>
|
||||
fun starr(@Path("id") id: String,
|
||||
@Query("username") username: String,
|
||||
@Query("password") password: String): Call<SuccessResponse>
|
||||
|
||||
|
||||
@POST("unstarr/{id}")
|
||||
fun unstarr(@Path("id") id: String, @Query("username") username: String, @Query("password") password: String): Call<SuccessResponse>
|
||||
fun unstarr(@Path("id") id: String,
|
||||
@Query("username") username: String,
|
||||
@Query("password") password: String): Call<SuccessResponse>
|
||||
|
||||
|
||||
@GET("stats")
|
||||
fun stats(@Query("username") username: String, @Query("password") password: String): Call<Stats>
|
||||
fun stats(@Query("username") username: String,
|
||||
@Query("password") password: String): Call<Stats>
|
||||
|
||||
|
||||
@GET("tags")
|
||||
fun tags(@Query("username") username: String, @Query("password") password: String): Call<List<Tag>>
|
||||
fun tags(@Query("username") username: String,
|
||||
@Query("password") password: String): Call<List<Tag>>
|
||||
|
||||
|
||||
@GET("update")
|
||||
fun update(@Query("username") username: String, @Query("password") password: String): Call<String>
|
||||
fun update(@Query("username") username: String,
|
||||
@Query("password") password: String): Call<String>
|
||||
|
||||
@GET("sources/spouts")
|
||||
fun spouts(@Query("username") username: String, @Query("password") password: String): Call<Map<String, Spout>>
|
||||
fun spouts(@Query("username") username: String,
|
||||
@Query("password") password: String): Call<Map<String, Spout>>
|
||||
|
||||
@GET("sources/list")
|
||||
fun sources(@Query("username") username: String, @Query("password") password: String): Call<List<Sources>>
|
||||
fun sources(@Query("username") username: String,
|
||||
@Query("password") password: String): Call<List<Sources>>
|
||||
|
||||
|
||||
@DELETE("source/{id}")
|
||||
fun deleteSource(@Path("id") id: String, @Query("username") username: String, @Query("password") password: String): Call<SuccessResponse>
|
||||
fun deleteSource(@Path("id") id: String,
|
||||
@Query("username") username: String,
|
||||
@Query("password") password: String): Call<SuccessResponse>
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST("source")
|
||||
fun createSource(@Field("title") title: String, @Field("url") url: String, @Field("spout") spout: String, @Field("tags") tags: String, @Field("filter") filter: String, @Query("username") username: String, @Query("password") password: String): Call<SuccessResponse>
|
||||
fun createSource(@Field("title") title: String,
|
||||
@Field("url") url: String,
|
||||
@Field("spout") spout: String,
|
||||
@Field("tags") tags: String,
|
||||
@Field("filter") filter: String,
|
||||
@Query("username") username: String,
|
||||
@Query("password") password: String): Call<SuccessResponse>
|
||||
}
|
||||
// codebeat:disable[ARITY]
|
@ -162,18 +162,21 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
public static class LinksPreferenceFragment extends PreferenceFragment {
|
||||
public void openUrl(Uri uri) {
|
||||
Intent browserIntent = new Intent(Intent.ACTION_VIEW, uri);
|
||||
startActivity(browserIntent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
addPreferencesFromResource(R.xml.pref_links);
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
Preference tracker = findPreference( "trackerLink" );
|
||||
tracker.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
findPreference( "trackerLink" ).setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.tracker_url)));
|
||||
startActivity(browserIntent);
|
||||
openUrl(Uri.parse(getString(R.string.tracker_url)));
|
||||
return true;
|
||||
}
|
||||
});
|
||||
@ -181,8 +184,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
||||
findPreference("sourceLink").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.source_url)));
|
||||
startActivity(browserIntent);
|
||||
openUrl(Uri.parse(getString(R.string.source_url)));
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
@ -7,82 +7,117 @@ import android.net.Uri
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.text.TextUtils
|
||||
import android.util.Patterns
|
||||
import apps.amine.bou.readerforselfoss.BuildConfig
|
||||
import apps.amine.bou.readerforselfoss.R
|
||||
|
||||
import com.google.firebase.remoteconfig.FirebaseRemoteConfig
|
||||
import okhttp3.HttpUrl
|
||||
|
||||
private fun isStoreVersion(context: Context): Boolean {
|
||||
var result = false
|
||||
try {
|
||||
val installer = context.packageManager
|
||||
.getInstallerPackageName(context.packageName)
|
||||
result = !TextUtils.isEmpty(installer)
|
||||
} catch (e: Throwable) {
|
||||
}
|
||||
import apps.amine.bou.readerforselfoss.BuildConfig
|
||||
import apps.amine.bou.readerforselfoss.R
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.Item
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
fun checkAndDisplayStoreApk(context: Context) =
|
||||
if (!isStoreVersion(context) && !BuildConfig.GITHUB_VERSION) {
|
||||
val alertDialog = AlertDialog.Builder(context).create()
|
||||
alertDialog.setTitle(context.getString(R.string.warning_version))
|
||||
alertDialog.setMessage(context.getString(R.string.text_version))
|
||||
fun Context.checkAndDisplayStoreApk() = {
|
||||
fun isStoreVersion(): Boolean =
|
||||
try {
|
||||
val installer = this.packageManager
|
||||
.getInstallerPackageName(this.packageName)
|
||||
!TextUtils.isEmpty(installer)
|
||||
} catch (e: Throwable) {
|
||||
false
|
||||
}
|
||||
|
||||
if (!isStoreVersion() && !BuildConfig.GITHUB_VERSION) {
|
||||
val alertDialog = AlertDialog.Builder(this).create()
|
||||
alertDialog.setTitle(getString(R.string.warning_version))
|
||||
alertDialog.setMessage(getString(R.string.text_version))
|
||||
alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "OK",
|
||||
{ dialog, _ -> dialog.dismiss() })
|
||||
{ dialog, _ -> dialog.dismiss() })
|
||||
alertDialog.show()
|
||||
} else Unit
|
||||
}
|
||||
|
||||
|
||||
fun isUrlValid(url: String): Boolean {
|
||||
val baseUrl = HttpUrl.parse(url)
|
||||
fun String.isUrlValid(): Boolean {
|
||||
val baseUrl = HttpUrl.parse(this)
|
||||
var existsAndEndsWithSlash = false
|
||||
if (baseUrl != null) {
|
||||
val pathSegments = baseUrl.pathSegments()
|
||||
existsAndEndsWithSlash = "" == pathSegments[pathSegments.size - 1]
|
||||
}
|
||||
|
||||
return Patterns.WEB_URL.matcher(url).matches() && existsAndEndsWithSlash
|
||||
return Patterns.WEB_URL.matcher(this).matches() && existsAndEndsWithSlash
|
||||
}
|
||||
|
||||
fun isEmptyOrNullOrNullString(str: String?): Boolean =
|
||||
str == null || str == "null" || str.isEmpty()
|
||||
fun String?.isEmptyOrNullOrNullString(): Boolean =
|
||||
this == null || this == "null" || this.isEmpty()
|
||||
|
||||
fun checkApkVersion(settings: SharedPreferences, editor: SharedPreferences.Editor, context: Context, mFirebaseRemoteConfig: FirebaseRemoteConfig) {
|
||||
mFirebaseRemoteConfig.fetch(43200)
|
||||
.addOnCompleteListener { task ->
|
||||
if (task.isSuccessful) {
|
||||
mFirebaseRemoteConfig.activateFetched()
|
||||
} else {
|
||||
fun Context.checkApkVersion(settings: SharedPreferences,
|
||||
editor: SharedPreferences.Editor,
|
||||
mFirebaseRemoteConfig: FirebaseRemoteConfig) = {
|
||||
fun isThereAnUpdate() {
|
||||
val APK_LINK = "github_apk"
|
||||
|
||||
val apkLink = mFirebaseRemoteConfig.getString(APK_LINK)
|
||||
val storedLink = settings.getString(APK_LINK, "")
|
||||
if (apkLink != storedLink && !apkLink.isEmpty()) {
|
||||
val alertDialog = AlertDialog.Builder(this).create()
|
||||
alertDialog.setTitle(getString(R.string.new_apk_available_title))
|
||||
alertDialog.setMessage(getString(R.string.new_apk_available_message))
|
||||
alertDialog.setButton(AlertDialog.BUTTON_POSITIVE, getString(R.string.new_apk_available_get)) { _, _ ->
|
||||
editor.putString(APK_LINK, apkLink)
|
||||
editor.apply()
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(apkLink))
|
||||
startActivity(browserIntent)
|
||||
}
|
||||
|
||||
isThereAnUpdate(settings, editor, context, mFirebaseRemoteConfig)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isThereAnUpdate(settings: SharedPreferences, editor: SharedPreferences.Editor, context: Context, mFirebaseRemoteConfig: FirebaseRemoteConfig) {
|
||||
val APK_LINK = "github_apk"
|
||||
|
||||
val apkLink = mFirebaseRemoteConfig.getString(APK_LINK)
|
||||
val storedLink = settings.getString(APK_LINK, "")
|
||||
if (apkLink != storedLink && !apkLink.isEmpty()) {
|
||||
val alertDialog = AlertDialog.Builder(context).create()
|
||||
alertDialog.setTitle(context.getString(R.string.new_apk_available_title))
|
||||
alertDialog.setMessage(context.getString(R.string.new_apk_available_message))
|
||||
alertDialog.setButton(AlertDialog.BUTTON_POSITIVE, context.getString(R.string.new_apk_available_get)) { _, _ ->
|
||||
editor.putString(APK_LINK, apkLink)
|
||||
editor.apply()
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(apkLink))
|
||||
context.startActivity(browserIntent)
|
||||
}
|
||||
alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, context.getString(R.string.new_apk_available_no),
|
||||
alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, getString(R.string.new_apk_available_no),
|
||||
{ dialog, _ ->
|
||||
editor.putString(APK_LINK, apkLink)
|
||||
editor.apply()
|
||||
dialog.dismiss()
|
||||
})
|
||||
alertDialog.show()
|
||||
alertDialog.show()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
mFirebaseRemoteConfig.fetch(43200)
|
||||
.addOnCompleteListener { task ->
|
||||
if (task.isSuccessful) {
|
||||
mFirebaseRemoteConfig.activateFetched()
|
||||
}
|
||||
|
||||
isThereAnUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
fun String.longHash(): Long {
|
||||
var h = 98764321261L
|
||||
val l = this.length
|
||||
val chars = this.toCharArray()
|
||||
|
||||
for (i in 0..l - 1) {
|
||||
h = 31 * h + chars[i].toLong()
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
fun String.toStringUriWithHttp() =
|
||||
if (!this.startsWith("https://") && !this.startsWith("http://"))
|
||||
"http://" + this
|
||||
else
|
||||
this
|
||||
|
||||
fun Context.shareLink(itemUrl: String) {
|
||||
val sendIntent = Intent()
|
||||
sendIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
sendIntent.action = Intent.ACTION_SEND
|
||||
sendIntent.putExtra(Intent.EXTRA_TEXT, itemUrl.toStringUriWithHttp())
|
||||
sendIntent.type = "text/plain"
|
||||
startActivity(Intent.createChooser(sendIntent, getString(R.string.share)).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
|
||||
}
|
||||
|
||||
fun Context.openInBrowser(i: Item) {
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
intent.data = Uri.parse(i.getLinkDecoded().toStringUriWithHttp())
|
||||
startActivity(intent)
|
||||
}
|
@ -1,16 +1,15 @@
|
||||
package apps.amine.bou.readerforselfoss.utils
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import apps.amine.bou.readerforselfoss.LoginActivity
|
||||
|
||||
|
||||
class Config(c: Context) {
|
||||
|
||||
private val settings: SharedPreferences
|
||||
|
||||
init {
|
||||
this.settings = c.getSharedPreferences(settingsName, Context.MODE_PRIVATE)
|
||||
}
|
||||
val settings: SharedPreferences = c.getSharedPreferences(settingsName, Context.MODE_PRIVATE)
|
||||
|
||||
val baseUrl: String
|
||||
get() = settings.getString("url", "")
|
||||
@ -27,8 +26,24 @@ class Config(c: Context) {
|
||||
val httpUserPassword: String
|
||||
get() = settings.getString("httpPassword", "")
|
||||
|
||||
|
||||
companion object {
|
||||
val settingsName = "paramsselfoss"
|
||||
|
||||
fun logoutAndRedirect(c: Context,
|
||||
callingActivity: Activity,
|
||||
editor: SharedPreferences.Editor,
|
||||
baseUrlFail: Boolean = false): Boolean {
|
||||
editor.remove("url")
|
||||
editor.remove("login")
|
||||
editor.remove("password")
|
||||
editor.apply()
|
||||
val intent = Intent(c, LoginActivity::class.java)
|
||||
if (baseUrlFail)
|
||||
intent.putExtra("baseUrlFail", baseUrlFail)
|
||||
c.startActivity(intent)
|
||||
callingActivity.finish()
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,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)
|
||||
}
|
||||
})
|
@ -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
|
||||
}
|
@ -7,62 +7,63 @@ import android.content.Intent
|
||||
import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
import android.support.customtabs.CustomTabsIntent
|
||||
|
||||
import xyz.klinker.android.drag_dismiss.DragDismissIntentBuilder
|
||||
|
||||
import apps.amine.bou.readerforselfoss.R
|
||||
import apps.amine.bou.readerforselfoss.ReaderActivity
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.Item
|
||||
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
|
||||
import xyz.klinker.android.drag_dismiss.DragDismissIntentBuilder
|
||||
|
||||
fun buildCustomTabsIntent(c: Context): CustomTabsIntent {
|
||||
|
||||
fun createPendingShareIntent(c: Context): PendingIntent {
|
||||
val actionIntent = Intent(Intent.ACTION_SEND)
|
||||
actionIntent.type = "text/plain"
|
||||
return PendingIntent.getActivity(
|
||||
c, 0, actionIntent, 0)
|
||||
}
|
||||
|
||||
fun Context.buildCustomTabsIntent(): CustomTabsIntent {
|
||||
|
||||
val actionIntent = Intent(Intent.ACTION_SEND)
|
||||
actionIntent.type = "text/plain"
|
||||
val createPendingShareIntent: PendingIntent = PendingIntent.getActivity(this, 0, actionIntent, 0)
|
||||
|
||||
|
||||
val intentBuilder = CustomTabsIntent.Builder()
|
||||
|
||||
// TODO: change to primary when it's possible to customize custom tabs title color
|
||||
//intentBuilder.setToolbarColor(c.getResources().getColor(R.color.colorPrimary));
|
||||
intentBuilder.setToolbarColor(c.resources.getColor(R.color.colorAccentDark))
|
||||
intentBuilder.setToolbarColor(resources.getColor(R.color.colorAccentDark))
|
||||
intentBuilder.setShowTitle(true)
|
||||
|
||||
|
||||
intentBuilder.setStartAnimations(c,
|
||||
intentBuilder.setStartAnimations(this,
|
||||
R.anim.slide_in_right,
|
||||
R.anim.slide_out_left)
|
||||
intentBuilder.setExitAnimations(c,
|
||||
intentBuilder.setExitAnimations(this,
|
||||
android.R.anim.slide_in_left,
|
||||
android.R.anim.slide_out_right)
|
||||
|
||||
val closeicon = BitmapFactory.decodeResource(c.resources, R.drawable.ic_close_white_24dp)
|
||||
val closeicon = BitmapFactory.decodeResource(resources, R.drawable.ic_close_white_24dp)
|
||||
intentBuilder.setCloseButtonIcon(closeicon)
|
||||
|
||||
val shareLabel = c.getString(R.string.label_share)
|
||||
val icon = BitmapFactory.decodeResource(c.resources,
|
||||
val shareLabel = this.getString(R.string.label_share)
|
||||
val icon = BitmapFactory.decodeResource(resources,
|
||||
R.drawable.ic_share_white_24dp)
|
||||
intentBuilder.setActionButton(icon, shareLabel, createPendingShareIntent(c))
|
||||
intentBuilder.setActionButton(icon, shareLabel, createPendingShareIntent)
|
||||
|
||||
return intentBuilder.build()
|
||||
}
|
||||
|
||||
fun openItemUrl(i: Item,
|
||||
customTabsIntent: CustomTabsIntent,
|
||||
internalBrowser: Boolean,
|
||||
articleViewer: Boolean,
|
||||
app: Activity,
|
||||
c: Context) {
|
||||
fun Context.openItemUrl(i: Item,
|
||||
customTabsIntent: CustomTabsIntent,
|
||||
internalBrowser: Boolean,
|
||||
articleViewer: Boolean,
|
||||
app: Activity) {
|
||||
if (!internalBrowser) {
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.data = Uri.parse(i.getLinkDecoded())
|
||||
app.startActivity(intent)
|
||||
} else {
|
||||
if (articleViewer) {
|
||||
val intent = Intent(c, ReaderActivity::class.java)
|
||||
val intent = Intent(this, ReaderActivity::class.java)
|
||||
|
||||
DragDismissIntentBuilder(c)
|
||||
DragDismissIntentBuilder(this)
|
||||
.setFullscreenOnTablets(true) // defaults to false, tablets will have padding on each side
|
||||
.setDragElasticity(DragDismissIntentBuilder.DragElasticity.NORMAL) // Larger elasticities will make it easier to dismiss.
|
||||
.build(intent)
|
||||
@ -74,7 +75,7 @@ fun openItemUrl(i: Item,
|
||||
) { _, uri ->
|
||||
val intent = Intent(Intent.ACTION_VIEW, uri)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
c.startActivity(intent)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,17 @@
|
||||
/* From https://github.com/mikepenz/MaterialDrawer/blob/develop/app/src/main/java/com/mikepenz/materialdrawer/app/drawerItems/CustomBaseViewHolder.java */
|
||||
package apps.amine.bou.readerforselfoss.utils.drawer
|
||||
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
|
||||
import apps.amine.bou.readerforselfoss.R
|
||||
|
||||
|
||||
|
||||
open class CustomBaseViewHolder(var view: View) : RecyclerView.ViewHolder(view) {
|
||||
var icon: ImageView = view.findViewById(R.id.material_drawer_icon)
|
||||
var name: TextView = view.findViewById(R.id.material_drawer_name)
|
||||
var description: TextView = view.findViewById(R.id.material_drawer_description)
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
/* From https://github.com/mikepenz/MaterialDrawer/blob/develop/app/src/main/java/com/mikepenz/materialdrawer/app/drawerItems/CustomUrlBasePrimaryDrawerItem.java */
|
||||
package apps.amine.bou.readerforselfoss.utils.drawer
|
||||
|
||||
import android.net.Uri
|
||||
import android.support.annotation.ColorInt
|
||||
import android.support.annotation.ColorRes
|
||||
import android.support.annotation.StringRes
|
||||
import android.support.v7.widget.RecyclerView
|
||||
|
||||
import com.mikepenz.materialdrawer.holder.ColorHolder
|
||||
import com.mikepenz.materialdrawer.holder.ImageHolder
|
||||
import com.mikepenz.materialdrawer.holder.StringHolder
|
||||
import com.mikepenz.materialdrawer.model.BaseDrawerItem
|
||||
import com.mikepenz.materialdrawer.util.DrawerImageLoader
|
||||
import com.mikepenz.materialdrawer.util.DrawerUIUtils
|
||||
import com.mikepenz.materialize.util.UIUtils
|
||||
|
||||
|
||||
|
||||
abstract class CustomUrlBasePrimaryDrawerItem<T, VH : RecyclerView.ViewHolder> : BaseDrawerItem<T, VH>() {
|
||||
fun withIcon(url: String): T {
|
||||
this.icon = ImageHolder(url)
|
||||
return this as T
|
||||
}
|
||||
|
||||
fun withIcon(uri: Uri): T {
|
||||
this.icon = ImageHolder(uri)
|
||||
return this as T
|
||||
}
|
||||
|
||||
var description: StringHolder? = null
|
||||
private set
|
||||
var descriptionTextColor: ColorHolder? = null
|
||||
private set
|
||||
|
||||
fun withDescription(description: String): T {
|
||||
this.description = StringHolder(description)
|
||||
return this as T
|
||||
}
|
||||
|
||||
fun withDescription(@StringRes descriptionRes: Int): T {
|
||||
this.description = StringHolder(descriptionRes)
|
||||
return this as T
|
||||
}
|
||||
|
||||
fun withDescriptionTextColor(@ColorInt color: Int): T {
|
||||
this.descriptionTextColor = ColorHolder.fromColor(color)
|
||||
return this as T
|
||||
}
|
||||
|
||||
fun withDescriptionTextColorRes(@ColorRes colorRes: Int): T {
|
||||
this.descriptionTextColor = ColorHolder.fromColorRes(colorRes)
|
||||
return this as T
|
||||
}
|
||||
|
||||
/**
|
||||
* a helper method to have the logic for all secondaryDrawerItems only once
|
||||
|
||||
* @param viewHolder
|
||||
*/
|
||||
protected fun bindViewHelper(viewHolder: CustomBaseViewHolder) {
|
||||
val ctx = viewHolder.itemView.context
|
||||
|
||||
//set the identifier from the drawerItem here. It can be used to run tests
|
||||
viewHolder.itemView.id = hashCode()
|
||||
|
||||
//set the item selected if it is
|
||||
viewHolder.itemView.isSelected = isSelected
|
||||
|
||||
//get the correct color for the background
|
||||
val selectedColor = getSelectedColor(ctx)
|
||||
//get the correct color for the text
|
||||
val color = getColor(ctx)
|
||||
val selectedTextColor = getSelectedTextColor(ctx)
|
||||
//get the correct color for the icon
|
||||
val iconColor = getIconColor(ctx)
|
||||
val selectedIconColor = getSelectedIconColor(ctx)
|
||||
|
||||
//set the background for the item
|
||||
UIUtils.setBackground(viewHolder.view, UIUtils.getSelectableBackground(ctx, selectedColor, true))
|
||||
//set the text for the name
|
||||
StringHolder.applyTo(this.getName(), viewHolder.name)
|
||||
//set the text for the description or hide
|
||||
StringHolder.applyToOrHide(this.description, viewHolder.description)
|
||||
|
||||
//set the colors for textViews
|
||||
viewHolder.name.setTextColor(getTextColorStateList(color, selectedTextColor))
|
||||
//set the description text color
|
||||
ColorHolder.applyToOr(descriptionTextColor,
|
||||
viewHolder.description, getTextColorStateList(color, selectedTextColor))
|
||||
|
||||
//define the typeface for our textViews
|
||||
if (getTypeface() != null) {
|
||||
viewHolder.name.typeface = getTypeface()
|
||||
viewHolder.description.typeface = getTypeface()
|
||||
}
|
||||
|
||||
//we make sure we reset the image first before setting the new one in case there is an empty one
|
||||
DrawerImageLoader.getInstance().cancelImage(viewHolder.icon)
|
||||
viewHolder.icon.setImageBitmap(null)
|
||||
//get the drawables for our icon and set it
|
||||
ImageHolder.applyTo(icon, viewHolder.icon, "customUrlItem")
|
||||
|
||||
//for android API 17 --> Padding not applied via xml
|
||||
DrawerUIUtils.setDrawerVerticalPadding(viewHolder.view)
|
||||
}
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
/* From https://github.com/mikepenz/MaterialDrawer/blob/develop/app/src/main/java/com/mikepenz/materialdrawer/app/drawerItems/CustomUrlPrimaryDrawerItem.java */
|
||||
package apps.amine.bou.readerforselfoss.utils.drawer
|
||||
|
||||
import android.support.annotation.LayoutRes
|
||||
import android.support.annotation.StringRes
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
|
||||
import com.mikepenz.materialdrawer.holder.BadgeStyle
|
||||
import com.mikepenz.materialdrawer.holder.StringHolder
|
||||
import com.mikepenz.materialdrawer.model.interfaces.ColorfulBadgeable
|
||||
|
||||
import apps.amine.bou.readerforselfoss.R
|
||||
|
||||
|
||||
|
||||
class CustomUrlPrimaryDrawerItem : CustomUrlBasePrimaryDrawerItem<CustomUrlPrimaryDrawerItem, CustomUrlPrimaryDrawerItem.ViewHolder>(), ColorfulBadgeable<CustomUrlPrimaryDrawerItem> {
|
||||
protected var mBadge: StringHolder = StringHolder("")
|
||||
protected var mBadgeStyle = BadgeStyle()
|
||||
|
||||
override fun withBadge(badge: StringHolder): CustomUrlPrimaryDrawerItem {
|
||||
this.mBadge = badge
|
||||
return this
|
||||
}
|
||||
|
||||
override fun withBadge(badge: String): CustomUrlPrimaryDrawerItem {
|
||||
this.mBadge = StringHolder(badge)
|
||||
return this
|
||||
}
|
||||
|
||||
override fun withBadge(@StringRes badgeRes: Int): CustomUrlPrimaryDrawerItem {
|
||||
this.mBadge = StringHolder(badgeRes)
|
||||
return this
|
||||
}
|
||||
|
||||
override fun withBadgeStyle(badgeStyle: BadgeStyle): CustomUrlPrimaryDrawerItem {
|
||||
this.mBadgeStyle = badgeStyle
|
||||
return this
|
||||
}
|
||||
|
||||
override fun getBadge(): StringHolder {
|
||||
return mBadge
|
||||
}
|
||||
|
||||
override fun getBadgeStyle(): BadgeStyle {
|
||||
return mBadgeStyle
|
||||
}
|
||||
|
||||
override fun getType(): Int {
|
||||
return R.id.material_drawer_item_custom_url_item
|
||||
}
|
||||
|
||||
@LayoutRes
|
||||
override fun getLayoutRes(): Int {
|
||||
return R.layout.material_drawer_item_primary
|
||||
}
|
||||
|
||||
override fun bindView(viewHolder: ViewHolder, payloads: List<*>?) {
|
||||
super.bindView(viewHolder, payloads)
|
||||
|
||||
val ctx = viewHolder.itemView.context
|
||||
|
||||
//bind the basic view parts
|
||||
bindViewHelper(viewHolder)
|
||||
|
||||
//set the text for the badge or hide
|
||||
val badgeVisible = StringHolder.applyToOrHide(mBadge, viewHolder.badge)
|
||||
//style the badge if it is visible
|
||||
if (badgeVisible) {
|
||||
mBadgeStyle.style(viewHolder.badge, getTextColorStateList(getColor(ctx), getSelectedTextColor(ctx)))
|
||||
viewHolder.badgeContainer.visibility = View.VISIBLE
|
||||
} else {
|
||||
viewHolder.badgeContainer.visibility = View.GONE
|
||||
}
|
||||
|
||||
//define the typeface for our textViews
|
||||
if (getTypeface() != null) {
|
||||
viewHolder.badge.typeface = getTypeface()
|
||||
}
|
||||
|
||||
//call the onPostBindView method to trigger post bind view actions (like the listener to modify the item if required)
|
||||
onPostBindView(this, viewHolder.itemView)
|
||||
}
|
||||
|
||||
override fun getViewHolder(v: View): ViewHolder {
|
||||
return ViewHolder(v)
|
||||
}
|
||||
|
||||
class ViewHolder(view: View) : CustomBaseViewHolder(view) {
|
||||
val badgeContainer: View = view.findViewById(R.id.material_drawer_badge_container)
|
||||
val badge: TextView = view.findViewById(R.id.material_drawer_badge)
|
||||
|
||||
}
|
||||
}
|
9
app/src/main/res/drawable-anydpi/ic_info_outline.xml
Normal file
9
app/src/main/res/drawable-anydpi/ic_info_outline.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/>
|
||||
</vector>
|
9
app/src/main/res/drawable-anydpi/ic_settings.xml
Normal file
9
app/src/main/res/drawable-anydpi/ic_settings.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z"/>
|
||||
</vector>
|
BIN
app/src/main/res/drawable-hdpi/ic_action_search.png
Normal file
BIN
app/src/main/res/drawable-hdpi/ic_action_search.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 680 B |
BIN
app/src/main/res/drawable-mdpi/ic_action_search.png
Normal file
BIN
app/src/main/res/drawable-mdpi/ic_action_search.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 442 B |
BIN
app/src/main/res/drawable-xhdpi/ic_action_search.png
Normal file
BIN
app/src/main/res/drawable-xhdpi/ic_action_search.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 634 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_action_search.png
Normal file
BIN
app/src/main/res/drawable-xxhdpi/ic_action_search.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
BIN
app/src/main/res/drawable-xxxhdpi/ic_action_search.png
Normal file
BIN
app/src/main/res/drawable-xxxhdpi/ic_action_search.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.3 KiB |
@ -5,6 +5,7 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:context="apps.amine.bou.readerforselfoss.HomeActivity">
|
||||
|
||||
<com.roughike.bottombar.BottomBar
|
||||
@ -54,19 +55,74 @@
|
||||
android:layout_toRightOf="@+id/bottomBar"
|
||||
android:layout_below="@id/prompt_view">
|
||||
|
||||
<android.support.v4.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swipeRefreshLayout"
|
||||
<android.support.design.widget.CoordinatorLayout
|
||||
android:id="@+id/intern_coordLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/my_recycler_view"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/background_grey"
|
||||
android:scrollbars="vertical"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||
</android.support.v4.widget.SwipeRefreshLayout>
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<android.support.design.widget.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:theme="@style/ToolBarStyle"
|
||||
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>
|
||||
|
||||
</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">
|
||||
|
||||
<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:textColor="@color/about_libraries_card_dark"
|
||||
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/background_grey"
|
||||
android:scrollbars="vertical"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</android.support.v4.widget.SwipeRefreshLayout>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</android.support.design.widget.CoordinatorLayout>
|
||||
|
||||
</android.support.design.widget.CoordinatorLayout>
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="apps.amine.bou.readerforselfoss.HomeActivity"
|
||||
android:fitsSystemWindows="true"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<com.github.stkent.amplify.prompt.DefaultLayoutPromptView
|
||||
@ -34,22 +35,75 @@
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@id/prompt_view">
|
||||
|
||||
|
||||
<android.support.v4.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swipeRefreshLayout"
|
||||
<android.support.design.widget.CoordinatorLayout
|
||||
android:id="@+id/intern_coordLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/my_recycler_view"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/background_grey"
|
||||
android:clipToPadding="false"
|
||||
android:scrollbars="vertical"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||
</android.support.v4.widget.SwipeRefreshLayout>
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<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"
|
||||
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>
|
||||
|
||||
</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="@color/background_grey">
|
||||
|
||||
<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:textColor="@color/about_libraries_card_dark"
|
||||
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.roughike.bottombar.BottomBar
|
||||
android:id="@+id/bottomBar"
|
||||
android:layout_width="match_parent"
|
||||
@ -62,6 +116,5 @@
|
||||
app:bb_badgeBackgroundColor="@color/colorPrimary"
|
||||
app:bb_inActiveTabColor="@color/black"
|
||||
app:bb_badgesHideWhenActive="false" />
|
||||
|
||||
</android.support.design.widget.CoordinatorLayout>
|
||||
</RelativeLayout>
|
@ -32,6 +32,7 @@
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/urlLayout"
|
||||
>
|
||||
|
||||
<EditText
|
||||
|
@ -2,36 +2,28 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item android:id="@+id/action_search"
|
||||
android:title="@string/menu_home_search"
|
||||
android:icon="@drawable/ic_action_search"
|
||||
app:showAsAction="always|collapseActionView"
|
||||
app:actionViewClass="android.support.v7.widget.SearchView" />
|
||||
|
||||
<item android:id="@+id/readAll"
|
||||
android:icon="@drawable/ic_done_all_white_24dp"
|
||||
android:title="@string/readAll"
|
||||
android:orderInCategory="1"
|
||||
app:showAsAction="ifRoom"/>
|
||||
app:showAsAction="always"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/refresh"
|
||||
android:icon="@drawable/ic_refresh"
|
||||
android:orderInCategory="99"
|
||||
android:title="@string/menu_home_refresh"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item android:id="@+id/action_settings"
|
||||
android:title="@string/title_activity_settings"
|
||||
android:orderInCategory="100"
|
||||
app:showAsAction="never"/>
|
||||
|
||||
<item android:id="@+id/action_sources"
|
||||
android:title="@string/action_source"
|
||||
android:orderInCategory="101"
|
||||
app:showAsAction="never"/>
|
||||
android:title="@string/menu_home_refresh" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_share_the_app"
|
||||
android:orderInCategory="102"
|
||||
android:title="@string/menu_share_the_app" />
|
||||
<item android:id="@+id/about"
|
||||
android:title="@string/action_about"
|
||||
android:orderInCategory="103"
|
||||
app:showAsAction="never"/>
|
||||
|
||||
<item android:id="@+id/action_disconnect"
|
||||
android:title="@string/action_disconnect"
|
||||
|
@ -15,7 +15,6 @@
|
||||
<string name="label_share">"Partager"</string>
|
||||
<string name="readAll">"Tout lire"</string>
|
||||
<string name="action_disconnect">"Déconnecter"</string>
|
||||
<string name="action_source">"Sources"</string>
|
||||
<string name="title_activity_settings">"Paramètres"</string>
|
||||
<string name="pref_header_general">"General"</string>
|
||||
<string name="pref_switch_actions_tap_title">"Action du clique sur un article"</string>
|
||||
@ -103,4 +102,18 @@
|
||||
<string name="card_height_off" >La taille de la carte sera fixe</string>
|
||||
<string name="source_code">Code source</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>
|
||||
</resources>
|
@ -15,7 +15,6 @@
|
||||
<string name="label_share">"Delen"</string>
|
||||
<string name="readAll">"Alles lezen"</string>
|
||||
<string name="action_disconnect">"Verbinding verbreken"</string>
|
||||
<string name="action_source">"Bronnen"</string>
|
||||
<string name="title_activity_settings">"Instellingen"</string>
|
||||
<string name="pref_header_general">"Algemeen"</string>
|
||||
<string name="pref_switch_actions_tap_title">"Actie bij tikken op artikelen"</string>
|
||||
@ -103,4 +102,18 @@
|
||||
<string name="card_height_off" >Vaste hoogte</string>
|
||||
<string name="source_code">Source code</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>
|
||||
</resources>
|
4
app/src/main/res/values/ids.xml
Normal file
4
app/src/main/res/values/ids.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<item name="material_drawer_item_custom_url_item" type="id" />
|
||||
</resources>
|
@ -1,4 +1,4 @@
|
||||
<resources>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<string name="app_name">"Reader for Selfoss"</string>
|
||||
<string name="title_activity_login">"Log in"</string>
|
||||
<string name="prompt_password">"Password"</string>
|
||||
@ -15,7 +15,6 @@
|
||||
<string name="label_share">"Share"</string>
|
||||
<string name="readAll">"Read all"</string>
|
||||
<string name="action_disconnect">"Disconnect"</string>
|
||||
<string name="action_source">"Sources"</string>
|
||||
<string name="title_activity_settings">"Settings"</string>
|
||||
<string name="pref_header_general">"General"</string>
|
||||
<string name="pref_switch_actions_tap_title">"Tap action on the articles"</string>
|
||||
@ -105,4 +104,18 @@
|
||||
<string name="card_height_off">Card height will be fixed</string>
|
||||
<string name="source_code">Source code</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>
|
||||
</resources>
|
@ -13,4 +13,20 @@
|
||||
<item name="android:windowBackground">@drawable/background_splash</item>
|
||||
</style>
|
||||
|
||||
<style name="NoBar" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<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>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
</resources>
|
||||
|
18
build.gradle
18
build.gradle
@ -1,18 +1,18 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.1.2-4'
|
||||
ext.kotlin_version = '1.1.3'
|
||||
repositories {
|
||||
maven { url 'https://maven.google.com' }
|
||||
jcenter()
|
||||
}
|
||||
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"
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// 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) {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
19
circle.yml
19
circle.yml
@ -30,9 +30,9 @@ dependencies:
|
||||
- ~/.android
|
||||
|
||||
override:
|
||||
- echo y | android update sdk --no-ui --filter "android-25,build-tools-25.0.3"
|
||||
- echo y | android update sdk --no-ui --filter "android-26,build-tools-26.0.0"
|
||||
- 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 "android-26, build-tools-26.0.0"
|
||||
- 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"
|
||||
@ -40,4 +40,17 @@ dependencies:
|
||||
|
||||
test:
|
||||
override:
|
||||
- gradlew assemble -P crashlyticsdemoApikey=$FABRIC_API_KEY -P crashlyticsdemoApisecret=$FABRIC_API_SECRET
|
||||
- (TERM="dumb" ./gradlew assemble --configure-on-demand --no-daemon -P crashlyticsdemoApikey=$FABRIC_API_KEY -P crashlyticsdemoApisecret=$FABRIC_API_SECRET -P appLoginUrl=$LOGIN_URL -P appLoginUsername=$LOGIN_USER_NAME -P appLoginPassword=$LOGIN_PASSWORD -PdisablePreDex -Pandroid.threadPoolSize=1 -Porg.gradle.parallel=false):
|
||||
timeout: 1440
|
||||
- emulator -avd circleci-android22 -no-window:
|
||||
background: true
|
||||
parallel: true
|
||||
- circle-android wait-for-boot
|
||||
- sleep 30
|
||||
- adb shell input keyevent 82
|
||||
- adb shell input tap 650 300
|
||||
- (TERM="dumb" ./gradlew connectedAndroidTest --configure-on-demand --no-daemon --stacktrace -P crashlyticsdemoApikey=$FABRIC_API_KEY -P crashlyticsdemoApisecret=$FABRIC_API_SECRET -P appLoginUrl=$LOGIN_URL -P appLoginUsername=$LOGIN_USER_NAME -P appLoginPassword=$LOGIN_PASSWORD -PdisablePreDex -Pandroid.threadPoolSize=1 -Porg.gradle.parallel=false):
|
||||
timeout: 1440
|
||||
- cp -r app/build/outputs $CIRCLE_ARTIFACTS
|
||||
- cp -r app/build/reports/androidTests $CIRCLE_ARTIFACTS
|
||||
- cp -r app/build/outputs/androidTest-results/* $CIRCLE_TEST_REPORTS
|
||||
|
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
||||
#Sat May 27 22:06:05 CEST 2017
|
||||
#Sun Jul 02 08:13:37 CEST 2017
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
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
|
||||
|
Reference in New Issue
Block a user