Compare commits

..

26 Commits

Author SHA1 Message Date
caef522c8b Changelog. 2017-11-28 20:16:09 +01:00
40ea07de2e Last fix for the endless scroll. 2017-11-28 20:10:32 +01:00
7905e4aa12 Fixes #127. 2017-11-28 19:18:43 +01:00
64f4fd708a Merge pull request #126 from aminecmi/translate_master
New Crowdin translations
2017-11-28 19:17:01 +01:00
b46e4a018f New translations strings.xml (Chinese Simplified) 2017-11-28 06:21:12 +01:00
3e999a9be2 New translations strings.xml (Chinese Simplified) 2017-11-28 06:11:20 +01:00
f656d621e6 New translations strings.xml (Chinese Simplified) 2017-11-28 06:01:37 +01:00
951cc1e6bd New translations strings.xml (Chinese Simplified) 2017-11-27 10:42:33 +01:00
d8d4264f1b New translations strings.xml (Chinese Simplified) 2017-11-27 10:33:30 +01:00
014eeec2b9 New translations strings.xml (Chinese Simplified) 2017-11-27 08:10:29 +01:00
83837bddc3 New translations strings.xml (Chinese Simplified) 2017-11-27 07:51:23 +01:00
f97666db92 New translations strings.xml (Chinese Simplified) 2017-11-27 07:00:51 +01:00
ee08ea41a1 New translations strings.xml (Chinese Simplified) 2017-11-27 06:50:49 +01:00
4ca64610cb New translations strings.xml (Chinese Simplified) 2017-11-27 06:40:37 +01:00
4980145e46 New translations strings.xml (Chinese Simplified) 2017-11-27 05:50:32 +01:00
10cbc19a0c New translations strings.xml (Chinese Simplified) 2017-11-27 05:40:32 +01:00
15fba2b29b New translations strings.xml (Chinese Simplified) 2017-11-27 05:20:28 +01:00
096952f88c New translations strings.xml (Chinese Simplified) 2017-11-27 05:10:31 +01:00
0ea70c1922 New translations strings.xml (Chinese Simplified) 2017-11-27 05:00:30 +01:00
69ac2e2b44 Kotlin style guide. (#125) 2017-11-25 13:12:12 +01:00
68098f4d84 Fixes #124. 2017-11-24 19:49:03 +01:00
080d52893e Changelog. 2017-11-23 21:57:10 +01:00
b02334a8d4 New Crowdin translations (#123)
* New translations strings.xml (Afrikaans)

* New translations strings.xml (Japanese)

* New translations strings.xml (Ukrainian)

* New translations strings.xml (Turkish)

* New translations strings.xml (Swedish)

* New translations strings.xml (Spanish)

* New translations strings.xml (Serbian (Cyrillic))

* New translations strings.xml (Russian)

* New translations strings.xml (Romanian)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Portuguese)

* New translations strings.xml (Polish)

* New translations strings.xml (Norwegian)

* New translations strings.xml (Korean)

* New translations strings.xml (Italian)

* New translations strings.xml (Arabic)

* New translations strings.xml (Hungarian)

* New translations strings.xml (Hebrew)

* New translations strings.xml (Greek)

* New translations strings.xml (German)

* New translations strings.xml (French)

* New translations strings.xml (Finnish)

* New translations strings.xml (Dutch)

* New translations strings.xml (Danish)

* New translations strings.xml (Czech)

* New translations strings.xml (Chinese Traditional)

* New translations strings.xml (Catalan)

* New translations strings.xml (Vietnamese)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Portuguese)

* New translations strings.xml (Portuguese, Brazilian)
2017-11-23 21:55:57 +01:00
27118add22 Closes #118. 2017-11-23 21:29:45 +01:00
2a6f98a1e8 Should fix #119. 2017-11-23 21:27:01 +01:00
1f67f2fdee Fixes #121. 2017-11-21 19:05:14 +01:00
69 changed files with 1432 additions and 948 deletions

View File

@ -1,3 +1,25 @@
**1.5.4.18**
- Typo fix.
- The real last infinite scroll bug fix.
- Simplified Chinese translation !
**1.5.4.17**
- Fixed the last bug with infinite scroll.
**1.5.4.16**
- Fixing list view displaying issues.
- Endless scroll is not in beta anymore.
**1.5.4.15**
- Fixed an issue with the sources list.
**1.5.4.14** **1.5.4.14**
- Fixing infinite scroll trying to load more items when there are no more. - Fixing infinite scroll trying to load more items when there are no more.

View File

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

View File

@ -5,29 +5,33 @@ import android.content.Intent
import android.support.test.InstrumentationRegistry import android.support.test.InstrumentationRegistry
import android.support.test.espresso.Espresso.onView import android.support.test.espresso.Espresso.onView
import android.support.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu import android.support.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
import android.support.test.espresso.action.ViewActions.* import android.support.test.espresso.action.ViewActions.click
import android.support.test.espresso.action.ViewActions.closeSoftKeyboard
import android.support.test.espresso.action.ViewActions.pressBack
import android.support.test.espresso.action.ViewActions.pressKey
import android.support.test.espresso.action.ViewActions.typeText
import android.support.test.espresso.assertion.ViewAssertions.matches import android.support.test.espresso.assertion.ViewAssertions.matches
import android.support.test.espresso.contrib.DrawerActions import android.support.test.espresso.contrib.DrawerActions
import android.support.test.espresso.intent.Intents import android.support.test.espresso.intent.Intents
import android.support.test.espresso.intent.Intents.intended import android.support.test.espresso.intent.Intents.intended
import android.support.test.espresso.intent.Intents.times import android.support.test.espresso.intent.Intents.times
import android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent import android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent
import android.support.test.espresso.matcher.ViewMatchers.* import android.support.test.espresso.matcher.ViewMatchers.isDisplayed
import android.support.test.espresso.matcher.ViewMatchers.isRoot
import android.support.test.espresso.matcher.ViewMatchers.withContentDescription
import android.support.test.espresso.matcher.ViewMatchers.withId
import android.support.test.espresso.matcher.ViewMatchers.withText
import android.support.test.rule.ActivityTestRule import android.support.test.rule.ActivityTestRule
import android.support.test.runner.AndroidJUnit4 import android.support.test.runner.AndroidJUnit4
import android.view.KeyEvent import android.view.KeyEvent
import apps.amine.bou.readerforselfoss.utils.Config
import com.mikepenz.aboutlibraries.ui.LibsActivity import com.heinrichreimersoftware.androidissuereporter.IssueReporterLauncher
import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import apps.amine.bou.readerforselfoss.utils.Config
import com.heinrichreimersoftware.androidissuereporter.IssueReporterLauncher
import org.junit.After
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class HomeActivityEspressoTest { class HomeActivityEspressoTest {
lateinit var context: Context lateinit var context: Context
@ -40,9 +44,9 @@ class HomeActivityEspressoTest {
context = InstrumentationRegistry.getInstrumentation().targetContext context = InstrumentationRegistry.getInstrumentation().targetContext
val editor = val editor =
context context
.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE) .getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
.edit() .edit()
editor.clear() editor.clear()
editor.putString("url", BuildConfig.LOGIN_URL) editor.putString("url", BuildConfig.LOGIN_URL)
@ -60,29 +64,32 @@ class HomeActivityEspressoTest {
rule.launchActivity(Intent()) rule.launchActivity(Intent())
onView( onView(
withMenu( withMenu(
id = R.id.action_search, id = R.id.action_search,
titleId = R.string.menu_home_search titleId = R.string.menu_home_search
) )
).perform(click()) ).perform(click())
onView(withId(R.id.search_bar)).check(matches(isDisplayed())) 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(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(withContentDescription(R.string.abc_toolbar_collapse_description)).perform(click())
openActionBarOverflowOrOptionsMenu(context) openActionBarOverflowOrOptionsMenu(context)
onView(withMenu(id = R.id.refresh, titleId = R.string.menu_home_refresh)) onView(withMenu(id = R.id.refresh, titleId = R.string.menu_home_refresh))
.perform(click()) .perform(click())
openActionBarOverflowOrOptionsMenu(context) openActionBarOverflowOrOptionsMenu(context)
onView(withText(R.string.action_disconnect)).perform(click()) onView(withText(R.string.action_disconnect)).perform(click())
intended(hasComponent(LoginActivity::class.java.name), times(1)) intended(hasComponent(LoginActivity::class.java.name), times(1))
} }
@Test @Test
@ -102,7 +109,6 @@ class HomeActivityEspressoTest {
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()) onView(withText(R.string.drawer_action_clear)).perform(click())
} }
// TODO: test articles opening and actions for cards and lists // TODO: test articles opening and actions for cards and lists

View File

@ -1,7 +1,5 @@
package apps.amine.bou.readerforselfoss package apps.amine.bou.readerforselfoss
import java.util.*
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.support.test.InstrumentationRegistry.getInstrumentation import android.support.test.InstrumentationRegistry.getInstrumentation
@ -11,22 +9,19 @@ import android.support.test.espresso.assertion.ViewAssertions.matches
import android.support.test.espresso.intent.Intents import android.support.test.espresso.intent.Intents
import android.support.test.espresso.intent.Intents.intended import android.support.test.espresso.intent.Intents.intended
import android.support.test.espresso.intent.Intents.times import android.support.test.espresso.intent.Intents.times
import android.support.test.espresso.intent.matcher.IntentMatchers.* import android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent
import android.support.test.espresso.intent.matcher.UriMatchers.hasHost import android.support.test.espresso.matcher.ViewMatchers.isDisplayed
import android.support.test.espresso.matcher.ViewMatchers.* import android.support.test.espresso.matcher.ViewMatchers.withId
import android.support.test.espresso.matcher.ViewMatchers.withText
import android.support.test.rule.ActivityTestRule import android.support.test.rule.ActivityTestRule
import android.support.test.runner.AndroidJUnit4 import android.support.test.runner.AndroidJUnit4
import apps.amine.bou.readerforselfoss.utils.Config
import org.hamcrest.Matchers.allOf import org.junit.After
import org.hamcrest.Matchers.equalTo
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import java.util.*
import apps.amine.bou.readerforselfoss.utils.Config
import org.junit.After
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class IntroActivityEspressoTest { class IntroActivityEspressoTest {
@ -37,9 +32,9 @@ class IntroActivityEspressoTest {
@Before @Before
fun clearData() { fun clearData() {
val editor = val editor =
getInstrumentation().targetContext getInstrumentation().targetContext
.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE) .getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
.edit() .edit()
editor.clear() editor.clear()
editor.commit() editor.commit()
@ -60,7 +55,6 @@ class IntroActivityEspressoTest {
intended(hasComponent(IntroActivity::class.java.name), times(1)) intended(hasComponent(IntroActivity::class.java.name), times(1))
intended(hasComponent(LoginActivity::class.java.name), times(1)) intended(hasComponent(LoginActivity::class.java.name), times(1))
} }
@Test @Test
@ -75,7 +69,7 @@ class IntroActivityEspressoTest {
onView(withText(R.string.intro_hello_title)).check(matches(isDisplayed())) onView(withText(R.string.intro_hello_title)).check(matches(isDisplayed()))
onView(withId(R.id.button_next)).perform(click()) onView(withId(R.id.button_next)).perform(click())
repeat(random) {_ -> repeat(random) { _ ->
onView(withText(R.string.intro_needs_selfoss_message)).check(matches(isDisplayed())) onView(withText(R.string.intro_needs_selfoss_message)).check(matches(isDisplayed()))
onView(withId(R.id.button_next)).perform(click()) onView(withId(R.id.button_next)).perform(click())
onView(withText(R.string.intro_all_set_message)).check(matches(isDisplayed())) onView(withText(R.string.intro_all_set_message)).check(matches(isDisplayed()))
@ -88,7 +82,6 @@ class IntroActivityEspressoTest {
intended(hasComponent(IntroActivity::class.java.name), times(1)) intended(hasComponent(IntroActivity::class.java.name), times(1))
intended(hasComponent(LoginActivity::class.java.name), times(1)) intended(hasComponent(LoginActivity::class.java.name), times(1))
} }
@After @After

View File

@ -5,27 +5,30 @@ import android.content.Intent
import android.support.test.InstrumentationRegistry import android.support.test.InstrumentationRegistry
import android.support.test.espresso.Espresso.onView import android.support.test.espresso.Espresso.onView
import android.support.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu import android.support.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
import android.support.test.espresso.action.ViewActions.* import android.support.test.espresso.action.ViewActions.click
import android.support.test.espresso.action.ViewActions.closeSoftKeyboard
import android.support.test.espresso.action.ViewActions.pressBack
import android.support.test.espresso.action.ViewActions.typeText
import android.support.test.espresso.assertion.ViewAssertions.matches import android.support.test.espresso.assertion.ViewAssertions.matches
import android.support.test.espresso.intent.Intents import android.support.test.espresso.intent.Intents
import android.support.test.espresso.intent.Intents.intended import android.support.test.espresso.intent.Intents.intended
import android.support.test.espresso.intent.Intents.times import android.support.test.espresso.intent.Intents.times
import android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent 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.espresso.matcher.ViewMatchers.* import android.support.test.espresso.matcher.ViewMatchers.isRoot
import android.support.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import android.support.test.espresso.matcher.ViewMatchers.withId
import android.support.test.espresso.matcher.ViewMatchers.withText
import android.support.test.rule.ActivityTestRule import android.support.test.rule.ActivityTestRule
import android.support.test.runner.AndroidJUnit4 import android.support.test.runner.AndroidJUnit4
import apps.amine.bou.readerforselfoss.utils.Config
import com.mikepenz.aboutlibraries.ui.LibsActivity import com.mikepenz.aboutlibraries.ui.LibsActivity
import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import apps.amine.bou.readerforselfoss.utils.Config
import org.junit.After
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class LoginActivityEspressoTest { class LoginActivityEspressoTest {
@ -69,10 +72,8 @@ class LoginActivityEspressoTest {
onView(isRoot()).perform(pressBack()) onView(isRoot()).perform(pressBack())
intended(hasComponent(LoginActivity::class.java.name)) intended(hasComponent(LoginActivity::class.java.name))
} }
@Test @Test
fun wrongLoginUrl() { fun wrongLoginUrl() {
rule.launchActivity(Intent()) rule.launchActivity(Intent())
@ -103,7 +104,10 @@ class LoginActivityEspressoTest {
onView(withId(R.id.loginLayout)).check(matches(isHintOrErrorEnabled())) onView(withId(R.id.loginLayout)).check(matches(isHintOrErrorEnabled()))
onView(withId(R.id.passwordLayout)).check(matches(isHintOrErrorEnabled())) onView(withId(R.id.passwordLayout)).check(matches(isHintOrErrorEnabled()))
onView(withId(R.id.loginView)).perform(click()).perform(typeText(username), closeSoftKeyboard()) onView(withId(R.id.loginView)).perform(click()).perform(
typeText(username),
closeSoftKeyboard()
)
onView(withId(R.id.passwordLayout)).check(matches(isHintOrErrorEnabled())) onView(withId(R.id.passwordLayout)).check(matches(isHintOrErrorEnabled()))
@ -111,9 +115,9 @@ class LoginActivityEspressoTest {
onView(withId(R.id.passwordLayout)).check( onView(withId(R.id.passwordLayout)).check(
matches( matches(
isHintOrErrorEnabled()) isHintOrErrorEnabled()
)
) )
} }
@Test @Test
@ -125,16 +129,21 @@ class LoginActivityEspressoTest {
onView(withId(R.id.withLogin)).perform(click()) onView(withId(R.id.withLogin)).perform(click())
onView(withId(R.id.loginView)).perform(click()).perform(typeText(username), closeSoftKeyboard()) onView(withId(R.id.loginView)).perform(click()).perform(
typeText(username),
closeSoftKeyboard()
)
onView(withId(R.id.passwordView)).perform(click()).perform(typeText("WRONGPASS"), closeSoftKeyboard()) onView(withId(R.id.passwordView)).perform(click()).perform(
typeText("WRONGPASS"),
closeSoftKeyboard()
)
onView(withId(R.id.signInButton)).perform(click()) onView(withId(R.id.signInButton)).perform(click())
onView(withId(R.id.urlLayout)).check(matches(isHintOrErrorEnabled())) onView(withId(R.id.urlLayout)).check(matches(isHintOrErrorEnabled()))
onView(withId(R.id.loginLayout)).check(matches(isHintOrErrorEnabled())) onView(withId(R.id.loginLayout)).check(matches(isHintOrErrorEnabled()))
onView(withId(R.id.passwordLayout)).check(matches(isHintOrErrorEnabled())) onView(withId(R.id.passwordLayout)).check(matches(isHintOrErrorEnabled()))
} }
@Test @Test
@ -146,19 +155,23 @@ class LoginActivityEspressoTest {
onView(withId(R.id.withLogin)).perform(click()) onView(withId(R.id.withLogin)).perform(click())
onView(withId(R.id.loginView)).perform(click()).perform(typeText(username), closeSoftKeyboard()) onView(withId(R.id.loginView)).perform(click()).perform(
typeText(username),
closeSoftKeyboard()
)
onView(withId(R.id.passwordView)).perform(click()).perform(typeText(password), closeSoftKeyboard()) onView(withId(R.id.passwordView)).perform(click()).perform(
typeText(password),
closeSoftKeyboard()
)
onView(withId(R.id.signInButton)).perform(click()) onView(withId(R.id.signInButton)).perform(click())
intended(hasComponent(HomeActivity::class.java.name)) intended(hasComponent(HomeActivity::class.java.name))
} }
@After @After
fun releaseIntents() { fun releaseIntents() {
Intents.release() Intents.release()
} }
} }

View File

@ -17,7 +17,6 @@ import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class MainActivityEspressoTest { class MainActivityEspressoTest {
@ -48,7 +47,6 @@ class MainActivityEspressoTest {
intended(hasComponent(MainActivity::class.java.name)) intended(hasComponent(MainActivity::class.java.name))
intended(hasComponent(IntroActivity::class.java.name)) intended(hasComponent(IntroActivity::class.java.name))
intended(hasComponent(LoginActivity::class.java.name), times(0)) intended(hasComponent(LoginActivity::class.java.name), times(0))
} }
@Test @Test
@ -61,13 +59,10 @@ class MainActivityEspressoTest {
intended(hasComponent(MainActivity::class.java.name)) intended(hasComponent(MainActivity::class.java.name))
intended(hasComponent(LoginActivity::class.java.name)) intended(hasComponent(LoginActivity::class.java.name))
intended(hasComponent(IntroActivity::class.java.name), times(0)) intended(hasComponent(IntroActivity::class.java.name), times(0))
} }
@After @After
fun releaseIntents() { fun releaseIntents() {
Intents.release() Intents.release()
} }
} }

View File

@ -1,31 +1,29 @@
package apps.amine.bou.readerforselfoss package apps.amine.bou.readerforselfoss
import android.view.View
import android.support.design.widget.TextInputLayout import android.support.design.widget.TextInputLayout
import android.support.test.espresso.matcher.ViewMatchers import android.support.test.espresso.matcher.ViewMatchers
import android.view.View
import org.hamcrest.Description import org.hamcrest.Description
import org.hamcrest.Matcher import org.hamcrest.Matcher
import org.hamcrest.TypeSafeMatcher
import org.hamcrest.Matchers import org.hamcrest.Matchers
import org.hamcrest.TypeSafeMatcher
fun isHintOrErrorEnabled(): Matcher<View> = fun isHintOrErrorEnabled(): Matcher<View> =
object : TypeSafeMatcher<View>() { object : TypeSafeMatcher<View>() {
override fun describeTo(description: Description?) {} override fun describeTo(description: Description?) {
override fun matchesSafely(item: View?): Boolean {
if (item !is TextInputLayout) {
return false
} }
return item.isHintEnabled || item.isErrorEnabled 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> = fun withMenu(id: Int, titleId: Int): Matcher<View> =
Matchers.anyOf( Matchers.anyOf(
ViewMatchers.withId(id), ViewMatchers.withId(id),
ViewMatchers.withText(titleId) ViewMatchers.withText(titleId)
) )

View File

@ -24,7 +24,6 @@ import retrofit2.Call
import retrofit2.Callback import retrofit2.Callback
import retrofit2.Response import retrofit2.Response
class AddSourceActivity : AppCompatActivity() { class AddSourceActivity : AppCompatActivity() {
private var mSpoutsValue: String? = null private var mSpoutsValue: String? = null
@ -67,7 +66,12 @@ class AddSourceActivity : AppCompatActivity() {
} }
} }
private fun handleSpoutsSpinner(spoutsSpinner: Spinner, api: SelfossApi?, mProgress: ProgressBar, formContainer: ConstraintLayout) { private fun handleSpoutsSpinner(
spoutsSpinner: Spinner,
api: SelfossApi?,
mProgress: ProgressBar,
formContainer: ConstraintLayout
) {
val spoutsKV = HashMap<String, String>() val spoutsKV = HashMap<String, String>()
spoutsSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { spoutsSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(adapterView: AdapterView<*>, view: View, i: Int, l: Long) { override fun onItemSelected(adapterView: AdapterView<*>, view: View, i: Int, l: Long) {
@ -82,7 +86,10 @@ class AddSourceActivity : AppCompatActivity() {
var items: Map<String, Spout> var items: Map<String, Spout>
api!!.spouts().enqueue(object : Callback<Map<String, Spout>> { api!!.spouts().enqueue(object : Callback<Map<String, Spout>> {
override fun onResponse(call: Call<Map<String, Spout>>, response: Response<Map<String, Spout>>) { override fun onResponse(
call: Call<Map<String, Spout>>,
response: Response<Map<String, Spout>>
) {
if (response.body() != null) { if (response.body() != null) {
items = response.body()!! items = response.body()!!
@ -98,10 +105,10 @@ class AddSourceActivity : AppCompatActivity() {
ArrayAdapter( ArrayAdapter(
this@AddSourceActivity, this@AddSourceActivity,
android.R.layout.simple_spinner_item, android.R.layout.simple_spinner_item,
itemsStrings) itemsStrings
)
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spoutsSpinner.adapter = spinnerArrayAdapter spoutsSpinner.adapter = spinnerArrayAdapter
} else { } else {
handleProblemWithSpouts() handleProblemWithSpouts()
} }
@ -112,13 +119,21 @@ class AddSourceActivity : AppCompatActivity() {
} }
private fun handleProblemWithSpouts() { private fun handleProblemWithSpouts() {
Toast.makeText(this@AddSourceActivity, R.string.cant_get_spouts, Toast.LENGTH_SHORT).show() Toast.makeText(
this@AddSourceActivity,
R.string.cant_get_spouts,
Toast.LENGTH_SHORT
).show()
mProgress.visibility = View.GONE mProgress.visibility = View.GONE
} }
}) })
} }
private fun maybeGetDetailsFromIntentSharing(intent: Intent, sourceUri: EditText, nameInput: EditText) { private fun maybeGetDetailsFromIntentSharing(
intent: Intent,
sourceUri: EditText,
nameInput: EditText
) {
if (Intent.ACTION_SEND == intent.action && "text/plain" == intent.type) { if (Intent.ACTION_SEND == intent.action && "text/plain" == intent.type) {
sourceUri.setText(intent.getStringExtra(Intent.EXTRA_TEXT)) sourceUri.setText(intent.getStringExtra(Intent.EXTRA_TEXT))
nameInput.setText(intent.getStringExtra(Intent.EXTRA_TITLE)) nameInput.setText(intent.getStringExtra(Intent.EXTRA_TITLE))
@ -146,16 +161,27 @@ class AddSourceActivity : AppCompatActivity() {
tags.text.toString(), tags.text.toString(),
"" ""
).enqueue(object : Callback<SuccessResponse> { ).enqueue(object : Callback<SuccessResponse> {
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) { override fun onResponse(
call: Call<SuccessResponse>,
response: Response<SuccessResponse>
) {
if (response.body() != null && response.body()!!.isSuccess) { if (response.body() != null && response.body()!!.isSuccess) {
finish() finish()
} else { } else {
Toast.makeText(this@AddSourceActivity, R.string.cant_create_source, Toast.LENGTH_SHORT).show() Toast.makeText(
this@AddSourceActivity,
R.string.cant_create_source,
Toast.LENGTH_SHORT
).show()
} }
} }
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) { override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
Toast.makeText(this@AddSourceActivity, R.string.cant_create_source, Toast.LENGTH_SHORT).show() Toast.makeText(
this@AddSourceActivity,
R.string.cant_create_source,
Toast.LENGTH_SHORT
).show()
} }
}) })
} }

View File

@ -11,11 +11,13 @@ import android.os.Bundle
import android.preference.PreferenceManager import android.preference.PreferenceManager
import android.support.v4.view.MenuItemCompat import android.support.v4.view.MenuItemCompat
import android.support.v7.app.AppCompatActivity import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.DividerItemDecoration
import android.support.v7.widget.GridLayoutManager import android.support.v7.widget.GridLayoutManager
import android.support.v7.widget.RecyclerView import android.support.v7.widget.RecyclerView
import android.support.v7.widget.SearchView import android.support.v7.widget.SearchView
import android.support.v7.widget.StaggeredGridLayoutManager import android.support.v7.widget.StaggeredGridLayoutManager
import android.support.v7.widget.helper.ItemTouchHelper import android.support.v7.widget.helper.ItemTouchHelper
import android.util.Log
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
@ -66,10 +68,10 @@ import com.mikepenz.materialdrawer.holder.BadgeStyle
import com.mikepenz.materialdrawer.model.DividerDrawerItem import com.mikepenz.materialdrawer.model.DividerDrawerItem
import com.mikepenz.materialdrawer.model.PrimaryDrawerItem import com.mikepenz.materialdrawer.model.PrimaryDrawerItem
import com.mikepenz.materialdrawer.model.SecondaryDrawerItem import com.mikepenz.materialdrawer.model.SecondaryDrawerItem
import kotlinx.android.synthetic.main.activity_home.*
import retrofit2.Call import retrofit2.Call
import retrofit2.Callback import retrofit2.Callback
import retrofit2.Response import retrofit2.Response
import kotlinx.android.synthetic.main.activity_home.*
class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener { class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
@ -100,6 +102,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
private var userIdentifier: String = "" private var userIdentifier: String = ""
private var displayAccountHeader: Boolean = false private var displayAccountHeader: Boolean = false
private var infiniteScroll: Boolean = false private var infiniteScroll: Boolean = false
private var lastFetchDone: Boolean = false
private lateinit var tabNewBadge: TextBadgeItem private lateinit var tabNewBadge: TextBadgeItem
private lateinit var tabArchiveBadge: TextBadgeItem private lateinit var tabArchiveBadge: TextBadgeItem
@ -120,13 +123,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
private var badgeAll: Int = -1 private var badgeAll: Int = -1
private var badgeFavs: Int = -1 private var badgeFavs: Int = -1
data class DrawerData(val tags: List<Tag>?, val sources: List<Sources>?) data class DrawerData(val tags: List<Tag>?, val sources: List<Sources>?)
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
customTabActivityHelper.bindCustomTabsService(this) customTabActivityHelper.bindCustomTabsService(this)
@ -150,10 +148,15 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
settings = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE) settings = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
sharedPref = PreferenceManager.getDefaultSharedPreferences(this) sharedPref = PreferenceManager.getDefaultSharedPreferences(this)
api = SelfossApi(this, this@HomeActivity, settings.getBoolean("isSelfSignedCert", false), sharedPref.getBoolean("should_log_everything", false)) api = SelfossApi(
this,
this@HomeActivity,
settings.getBoolean("isSelfSignedCert", false),
sharedPref.getBoolean("should_log_everything", false)
)
items = ArrayList() items = ArrayList()
appColors = AppColors(this@HomeActivity) appColors = AppColors(this@HomeActivity)
handleBottomBar() handleBottomBar()
handleDrawer() handleDrawer()
@ -166,21 +169,36 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
swipeRefreshLayout.setColorSchemeResources( swipeRefreshLayout.setColorSchemeResources(
R.color.refresh_progress_1, R.color.refresh_progress_1,
R.color.refresh_progress_2, R.color.refresh_progress_2,
R.color.refresh_progress_3) R.color.refresh_progress_3
)
swipeRefreshLayout.setOnRefreshListener { swipeRefreshLayout.setOnRefreshListener {
handleDrawerItems() handleDrawerItems()
getElementsAccordingToTab() getElementsAccordingToTab()
} }
val simpleItemTouchCallback = val simpleItemTouchCallback =
object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) { object : ItemTouchHelper.SimpleCallback(
0,
ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
) {
override fun getSwipeDirs(
recyclerView: RecyclerView?,
viewHolder: RecyclerView.ViewHolder?
): Int =
if (elementsShown != UNREAD_SHOWN) {
0
} else {
super.getSwipeDirs(
recyclerView,
viewHolder
)
}
override fun getSwipeDirs(recyclerView: RecyclerView?, viewHolder: RecyclerView.ViewHolder?): Int = override fun onMove(
if (elementsShown != UNREAD_SHOWN) 0 else super.getSwipeDirs(recyclerView, viewHolder) recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
override fun onMove(recyclerView: RecyclerView, target: RecyclerView.ViewHolder
viewHolder: RecyclerView.ViewHolder, ): Boolean = false
target: RecyclerView.ViewHolder): Boolean = false
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, swipeDir: Int) { override fun onSwiped(viewHolder: RecyclerView.ViewHolder, swipeDir: Int) {
try { try {
@ -196,28 +214,40 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
if (items.size > 0) { if (items.size > 0) {
badgeNew-- badgeNew--
reloadBadgeContent() reloadBadgeContent()
} } else {
else
tabNewBadge.hide() tabNewBadge.hide()
}
val manager = recyclerView.layoutManager val manager = recyclerView.layoutManager
val lastVisibleItem: Int = when (manager) { val lastVisibleItem: Int = when (manager) {
is StaggeredGridLayoutManager -> manager.findLastCompletelyVisibleItemPositions(null).last() is StaggeredGridLayoutManager -> manager.findLastCompletelyVisibleItemPositions(
null
).last()
is GridLayoutManager -> manager.findLastCompletelyVisibleItemPosition() is GridLayoutManager -> manager.findLastCompletelyVisibleItemPosition()
else -> 0 else -> 0
} }
if (lastVisibleItem === items.size && items.size <= maxItemNumber() && maxItemNumber() >= itemsNumber) { if (lastVisibleItem === items.size &&
getElementsAccordingToTab(appendResults = true, offsetOverride = lastVisibleItem) items.size <= maxItemNumber() &&
(maxItemNumber() >= itemsNumber || !lastFetchDone)
) {
if (maxItemNumber() < itemsNumber) {
lastFetchDone = true
}
getElementsAccordingToTab(
appendResults = true,
offsetOverride = lastVisibleItem
)
} }
} catch (e: IndexOutOfBoundsException) { } catch (e: IndexOutOfBoundsException) {
Crashlytics.setUserIdentifier(userIdentifier) Crashlytics.setUserIdentifier(userIdentifier)
Crashlytics.log(100, "SWIPE_INDEX_OUT_OF_BOUND", "IndexOutOfBoundsException when swiping") Crashlytics.log(
100,
"SWIPE_INDEX_OUT_OF_BOUND",
"IndexOutOfBoundsException when swiping"
)
Crashlytics.logException(e) Crashlytics.logException(e)
} }
} }
} }
@ -267,7 +297,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
bottomBar.setMode(BottomNavigationBar.MODE_SHIFTING) bottomBar.setMode(BottomNavigationBar.MODE_SHIFTING)
bottomBar.setBackgroundStyle(BottomNavigationBar.BACKGROUND_STYLE_STATIC) bottomBar.setBackgroundStyle(BottomNavigationBar.BACKGROUND_STYLE_STATIC)
} }
override fun onResume() { override fun onResume() {
@ -293,7 +322,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
customTabActivityHelper.unbindCustomTabsService(this) customTabActivityHelper.unbindCustomTabsService(this)
} }
private fun handleSharedPrefs() { private fun handleSharedPrefs() {
debugReadingItems = sharedPref.getBoolean("read_debug", false) debugReadingItems = sharedPref.getBoolean("read_debug", false)
clickBehavior = sharedPref.getBoolean("tab_on_tap", false) clickBehavior = sharedPref.getBoolean("tab_on_tap", false)
@ -330,7 +358,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
bottomBar.hide() bottomBar.hide()
} }
if (displayAccountHeader) if (displayAccountHeader) {
accountHeader { accountHeader {
background = R.drawable.bg background = R.drawable.bg
profile(settings.getString("url", "")) { profile(settings.getString("url", "")) {
@ -338,13 +366,17 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
} }
selectionListEnabledForSingleProfile = false selectionListEnabledForSingleProfile = false
} }
}
footer { footer {
primaryItem(R.string.drawer_report_bug) { primaryItem(R.string.drawer_report_bug) {
icon = R.drawable.ic_bug_report icon = R.drawable.ic_bug_report
iconTintingEnabled = true iconTintingEnabled = true
onClick { _ -> onClick { _ ->
IssueReporterLauncher.forTarget(getString(R.string.report_github_user), getString(R.string.report_github_repo)) IssueReporterLauncher.forTarget(
getString(R.string.report_github_user),
getString(R.string.report_github_repo)
)
.theme(R.style.Theme_App_Light) .theme(R.style.Theme_App_Light)
.guestToken(BuildConfig.GITHUB_TOKEN) .guestToken(BuildConfig.GITHUB_TOKEN)
.guestEmailRequired(true) .guestEmailRequired(true)
@ -373,21 +405,20 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
} }
} }
} }
} }
private fun handleDrawerItems() { private fun handleDrawerItems() {
fun handleDrawerData(maybeDrawerData: DrawerData?, loadedFromCache: Boolean = false) { fun handleDrawerData(maybeDrawerData: DrawerData?, loadedFromCache: Boolean = false) {
fun handleTags(maybeTags: List<Tag>?) { fun handleTags(maybeTags: List<Tag>?) {
if (maybeTags == null) { if (maybeTags == null) {
if (loadedFromCache) if (loadedFromCache) {
drawer.addItem( drawer.addItem(
SecondaryDrawerItem() SecondaryDrawerItem()
.withName(getString(R.string.drawer_error_loading_tags)) .withName(getString(R.string.drawer_error_loading_tags))
.withSelectable(false)) .withSelectable(false)
} )
else { }
} else {
for (tag in maybeTags) { for (tag in maybeTags) {
val gd = GradientDrawable() val gd = GradientDrawable()
gd.setColor(Color.parseColor(tag.color)) gd.setColor(Color.parseColor(tag.color))
@ -412,19 +443,19 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
) )
} }
} }
} }
fun handleSources(maybeSources: List<Sources>?) { fun handleSources(maybeSources: List<Sources>?) {
if (maybeSources == null) { if (maybeSources == null) {
if (loadedFromCache) if (loadedFromCache) {
drawer.addItem( drawer.addItem(
SecondaryDrawerItem() SecondaryDrawerItem()
.withName(getString(R.string.drawer_error_loading_sources)) .withName(getString(R.string.drawer_error_loading_sources))
.withSelectable(false)) .withSelectable(false)
} )
else }
for (tag in maybeSources) } else {
for (tag in maybeSources) {
drawer.addItem( drawer.addItem(
CustomUrlPrimaryDrawerItem() CustomUrlPrimaryDrawerItem()
.withName(tag.title) .withName(tag.title)
@ -436,7 +467,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
false false
} }
) )
}
}
} }
drawer.removeAllItems() drawer.removeAllItems()
@ -459,7 +491,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
SecondaryDrawerItem() SecondaryDrawerItem()
.withName(getString(R.string.drawer_item_tags)) .withName(getString(R.string.drawer_item_tags))
.withIdentifier(DRAWER_ID_TAGS) .withIdentifier(DRAWER_ID_TAGS)
.withSelectable(false)) .withSelectable(false)
)
handleTags(maybeDrawerData.tags) handleTags(maybeDrawerData.tags)
drawer.addItem(DividerDrawerItem()) drawer.addItem(DividerDrawerItem())
drawer.addItem( drawer.addItem(
@ -484,41 +517,46 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
.withOnDrawerItemClickListener { _, _, _ -> .withOnDrawerItemClickListener { _, _, _ ->
LibsBuilder() LibsBuilder()
.withActivityStyle( .withActivityStyle(
if (appColors.isDarkTheme) if (appColors.isDarkTheme) {
Libs.ActivityStyle.LIGHT_DARK_TOOLBAR Libs.ActivityStyle.LIGHT_DARK_TOOLBAR
else } else {
Libs.ActivityStyle.DARK Libs.ActivityStyle.DARK
}
) )
.withAboutIconShown(true) .withAboutIconShown(true)
.withAboutVersionShown(true) .withAboutVersionShown(true)
.start(this@HomeActivity) .start(this@HomeActivity)
false false
}) }
)
if (!loadedFromCache) if (!loadedFromCache) {
Reservoir.putAsync("drawerData", maybeDrawerData, object : ReservoirPutCallback { Reservoir.putAsync(
override fun onSuccess() {} "drawerData", maybeDrawerData, object : ReservoirPutCallback {
override fun onSuccess() {
}
override fun onFailure(p0: Exception?) { override fun onFailure(p0: Exception?) {
} }
}) })
}
} else { } else {
if (!loadedFromCache) { if (!loadedFromCache) {
drawer.addItem( drawer.addItem(
PrimaryDrawerItem() PrimaryDrawerItem()
.withName(getString(R.string.no_tags_loaded)) .withName(getString(R.string.no_tags_loaded))
.withIdentifier(DRAWER_ID_TAGS) .withIdentifier(DRAWER_ID_TAGS)
.withSelectable(false)) .withSelectable(false)
)
drawer.addItem( drawer.addItem(
PrimaryDrawerItem() PrimaryDrawerItem()
.withName(getString(R.string.no_sources_loaded)) .withName(getString(R.string.no_sources_loaded))
.withIdentifier(DRAWER_ID_SOURCES) .withIdentifier(DRAWER_ID_SOURCES)
.withSelectable(false)) .withSelectable(false)
)
} }
} }
} }
fun drawerApiCalls(maybeDrawerData: DrawerData?) { fun drawerApiCalls(maybeDrawerData: DrawerData?) {
@ -526,23 +564,28 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
var sources: List<Sources>? var sources: List<Sources>?
fun sourcesApiCall() { fun sourcesApiCall() {
api.sources.enqueue(object: Callback<List<Sources>> { api.sources.enqueue(object : Callback<List<Sources>> {
override fun onResponse(call: Call<List<Sources>>?, response: Response<List<Sources>>) { override fun onResponse(
call: Call<List<Sources>>?,
response: Response<List<Sources>>
) {
sources = response.body() sources = response.body()
val apiDrawerData = DrawerData(tags, sources) val apiDrawerData = DrawerData(tags, sources)
if ((maybeDrawerData != null && maybeDrawerData != apiDrawerData) || maybeDrawerData == null) if ((maybeDrawerData != null && maybeDrawerData != apiDrawerData) || maybeDrawerData == null) {
handleDrawerData(apiDrawerData) handleDrawerData(apiDrawerData)
}
} }
override fun onFailure(call: Call<List<Sources>>?, t: Throwable?) { override fun onFailure(call: Call<List<Sources>>?, t: Throwable?) {
} }
}) })
} }
api.tags.enqueue(object: Callback<List<Tag>> { api.tags.enqueue(object : Callback<List<Tag>> {
override fun onResponse(call: Call<List<Tag>>, response: Response<List<Tag>>) { override fun onResponse(
call: Call<List<Tag>>,
response: Response<List<Tag>>
) {
tags = response.body() tags = response.body()
sourcesApiCall() sourcesApiCall()
} }
@ -550,14 +593,18 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
override fun onFailure(call: Call<List<Tag>>?, t: Throwable?) { override fun onFailure(call: Call<List<Tag>>?, t: Throwable?) {
sourcesApiCall() sourcesApiCall()
} }
}) })
} }
drawer.addItem(PrimaryDrawerItem().withName(getString(R.string.drawer_loading)).withSelectable(false)) drawer.addItem(
PrimaryDrawerItem().withName(getString(R.string.drawer_loading)).withSelectable(
false
)
)
val resultType = object : TypeToken<DrawerData>() {}.type //NOSONAR val resultType = object : TypeToken<DrawerData>() {}.type //NOSONAR
Reservoir.getAsync("drawerData", resultType, object: ReservoirGetCallback<DrawerData> { Reservoir.getAsync(
"drawerData", resultType, object : ReservoirGetCallback<DrawerData> {
override fun onSuccess(maybeDrawerData: DrawerData?) { override fun onSuccess(maybeDrawerData: DrawerData?) {
handleDrawerData(maybeDrawerData, loadedFromCache = true) handleDrawerData(maybeDrawerData, loadedFromCache = true)
drawerApiCalls(maybeDrawerData) drawerApiCalls(maybeDrawerData)
@ -566,14 +613,16 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
override fun onFailure(p0: Exception?) { override fun onFailure(p0: Exception?) {
drawerApiCalls(null) drawerApiCalls(null)
} }
}) })
} }
private fun reloadLayoutManager() { private fun reloadLayoutManager() {
val mLayoutManager: RecyclerView.LayoutManager val mLayoutManager: RecyclerView.LayoutManager
if (shouldBeCardView) { if (shouldBeCardView) {
mLayoutManager = StaggeredGridLayoutManager(calculateNoOfColumns(), StaggeredGridLayoutManager.VERTICAL) mLayoutManager = StaggeredGridLayoutManager(
calculateNoOfColumns(),
StaggeredGridLayoutManager.VERTICAL
)
mLayoutManager.gapStrategy = StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS mLayoutManager.gapStrategy = StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
} else { } else {
mLayoutManager = GridLayoutManager(this, calculateNoOfColumns()) mLayoutManager = GridLayoutManager(this, calculateNoOfColumns())
@ -610,25 +659,29 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
else -> Unit else -> Unit
} }
override fun onTabSelected(position: Int) = override fun onTabSelected(position: Int) {
when (position) { offset = 0
0 -> getUnRead() lastFetchDone = false
1 -> getRead() when (position) {
2 -> getStarred() 0 -> getUnRead()
else -> Unit 1 -> getRead()
} 2 -> getStarred()
else -> Unit
}
}
}) })
} }
private fun handleInfiniteScroll() { private fun handleInfiniteScroll() {
if (recyclerViewScrollListener == null) if (recyclerViewScrollListener == null) {
recyclerViewScrollListener = object : RecyclerView.OnScrollListener() { recyclerViewScrollListener = object : RecyclerView.OnScrollListener() {
override fun onScrolled(localRecycler: RecyclerView?, dx: Int, dy: Int) { override fun onScrolled(localRecycler: RecyclerView?, dx: Int, dy: Int) {
if (localRecycler != null && dy > 0) { if (localRecycler != null && dy > 0) {
val manager = recyclerView.layoutManager val manager = recyclerView.layoutManager
val lastVisibleItem: Int = when (manager) { val lastVisibleItem: Int = when (manager) {
is StaggeredGridLayoutManager -> manager.findLastCompletelyVisibleItemPositions(null).last() is StaggeredGridLayoutManager -> manager.findLastCompletelyVisibleItemPositions(
null
).last()
is GridLayoutManager -> manager.findLastCompletelyVisibleItemPosition() is GridLayoutManager -> manager.findLastCompletelyVisibleItemPosition()
else -> 0 else -> 0
} }
@ -639,6 +692,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
} }
} }
} }
}
recyclerView.clearOnScrollListeners() recyclerView.clearOnScrollListeners()
recyclerView.addOnScrollListener(recyclerViewScrollListener) recyclerView.addOnScrollListener(recyclerViewScrollListener)
@ -653,8 +707,11 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
recyclerView.visibility = View.VISIBLE recyclerView.visibility = View.VISIBLE
} }
private fun getElementsAccordingToTab(appendResults: Boolean = false, offsetOverride: Int? = null) { private fun getElementsAccordingToTab(
offset = if (appendResults && offsetOverride === null) { appendResults: Boolean = false,
offsetOverride: Int? = null
) {
offset = if (appendResults && offsetOverride === null) {
(offset + itemsNumber) (offset + itemsNumber)
} else { } else {
offsetOverride ?: 0 offsetOverride ?: 0
@ -669,66 +726,107 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
} }
} }
private fun doCallTo(appendResults: Boolean, toastMessage: Int, call: (String?, Long?, String?) -> Call<List<Item>>) { private fun doCallTo(
appendResults: Boolean,
toastMessage: Int,
call: (String?, Long?, String?) -> Call<List<Item>>
) {
fun handleItemsResponse(response: Response<List<Item>>) { fun handleItemsResponse(response: Response<List<Item>>) {
val didUpdate = (response.body() != items) val didUpdate = (response.body() != items)
if (response.body() != null) { if (response.body() != null) {
if (response.body() != items) { if (response.body() != items) {
if (appendResults) if (appendResults) {
items.addAll(response.body() as ArrayList<Item>) items.addAll(response.body() as ArrayList<Item>)
else } else {
items = response.body() as ArrayList<Item> items = response.body() as ArrayList<Item>
}
} }
} else { } else {
if (!appendResults) if (!appendResults) {
items = ArrayList() items = ArrayList()
}
} }
if (didUpdate) if (didUpdate) {
handleListResult(appendResults) handleListResult(appendResults)
}
mayBeEmpty() mayBeEmpty()
swipeRefreshLayout.isRefreshing = false swipeRefreshLayout.isRefreshing = false
} }
if (!swipeRefreshLayout.isRefreshing) if (!swipeRefreshLayout.isRefreshing) {
swipeRefreshLayout.post { swipeRefreshLayout.isRefreshing = true } swipeRefreshLayout.post { swipeRefreshLayout.isRefreshing = true }
}
call(maybeTagFilter?.tag, maybeSourceFilter?.id?.toLong(), maybeSearchFilter) call(maybeTagFilter?.tag, maybeSourceFilter?.id?.toLong(), maybeSearchFilter)
.enqueue(object : Callback<List<Item>> { .enqueue(object : Callback<List<Item>> {
override fun onResponse(call: Call<List<Item>>, response: Response<List<Item>>) { override fun onResponse(
call: Call<List<Item>>,
response: Response<List<Item>>
) {
handleItemsResponse(response) handleItemsResponse(response)
} }
override fun onFailure(call: Call<List<Item>>, t: Throwable) { override fun onFailure(call: Call<List<Item>>, t: Throwable) {
swipeRefreshLayout.isRefreshing = false swipeRefreshLayout.isRefreshing = false
Toast.makeText(this@HomeActivity, toastMessage, Toast.LENGTH_SHORT).show() Toast.makeText(
this@HomeActivity,
toastMessage,
Toast.LENGTH_SHORT
).show()
} }
}) })
} }
private fun getUnRead(appendResults: Boolean = false) { private fun getUnRead(appendResults: Boolean = false) {
elementsShown = UNREAD_SHOWN elementsShown = UNREAD_SHOWN
doCallTo(appendResults, R.string.cant_get_new_elements){t, id, f -> api.newItems(t, id, f, itemsNumber, offset)} doCallTo(appendResults, R.string.cant_get_new_elements) { t, id, f ->
api.newItems(
t,
id,
f,
itemsNumber,
offset
)
}
} }
private fun getRead(appendResults: Boolean = false) { private fun getRead(appendResults: Boolean = false) {
elementsShown = READ_SHOWN elementsShown = READ_SHOWN
doCallTo(appendResults, R.string.cant_get_read){t, id, f -> api.readItems(t, id, f, itemsNumber, offset)} doCallTo(appendResults, R.string.cant_get_read) { t, id, f ->
api.readItems(
t,
id,
f,
itemsNumber,
offset
)
}
} }
private fun getStarred(appendResults: Boolean = false) { private fun getStarred(appendResults: Boolean = false) {
elementsShown = FAV_SHOWN elementsShown = FAV_SHOWN
doCallTo(appendResults, R.string.cant_get_favs){t, id, f -> api.starredItems(t, id, f, itemsNumber, offset)} doCallTo(appendResults, R.string.cant_get_favs) { t, id, f ->
api.starredItems(
t,
id,
f,
itemsNumber,
offset
)
}
} }
private fun handleListResult(appendResults: Boolean = false) { private fun handleListResult(appendResults: Boolean = false) {
if (appendResults) { if (appendResults) {
val oldManager = recyclerView.layoutManager val oldManager = recyclerView.layoutManager
firstVisible = if ((oldManager is StaggeredGridLayoutManager)) { firstVisible = when (oldManager) {
oldManager.findFirstCompletelyVisibleItemPositions(null).last() is StaggeredGridLayoutManager ->
oldManager.findFirstCompletelyVisibleItemPositions(null).last()
is GridLayoutManager ->
oldManager.findFirstCompletelyVisibleItemPosition()
else -> 0
} }
else
(oldManager as GridLayoutManager)?.findFirstCompletelyVisibleItemPosition()
} }
reloadLayoutManager() reloadLayoutManager()
@ -746,7 +844,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
fullHeightCards, fullHeightCards,
appColors, appColors,
debugReadingItems, debugReadingItems,
userIdentifier) userIdentifier
)
} else { } else {
mAdapter = mAdapter =
ItemListAdapter( ItemListAdapter(
@ -758,7 +857,15 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
internalBrowser, internalBrowser,
articleViewer, articleViewer,
debugReadingItems, debugReadingItems,
userIdentifier) userIdentifier
)
recyclerView.addItemDecoration(
DividerItemDecoration(
this@HomeActivity,
DividerItemDecoration.VERTICAL
)
)
} }
recyclerView.adapter = mAdapter recyclerView.adapter = mAdapter
mAdapter.notifyDataSetChanged() mAdapter.notifyDataSetChanged()
@ -783,7 +890,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
} }
} }
override fun onFailure(call: Call<Stats>, t: Throwable) {} override fun onFailure(call: Call<Stats>, t: Throwable) {
}
}) })
} else { } else {
reloadBadgeContent(succeeded = false) reloadBadgeContent(succeeded = false)
@ -792,10 +900,11 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
private fun reloadBadgeContent(succeeded: Boolean = true) { private fun reloadBadgeContent(succeeded: Boolean = true) {
if (succeeded) { if (succeeded) {
if (displayUnreadCount) if (displayUnreadCount) {
tabNewBadge tabNewBadge
.setText(badgeNew.toString()) .setText(badgeNew.toString())
.maybeShow() .maybeShow()
}
if (displayAllCount) { if (displayAllCount) {
tabArchiveBadge tabArchiveBadge
.setText(badgeAll.toString()) .setText(badgeAll.toString())
@ -849,7 +958,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
} }
else -> super.onActivityResult(req, result, data) else -> super.onActivityResult(req, result, data)
} }
} }
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
@ -867,14 +975,23 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
when (item.itemId) { when (item.itemId) {
R.id.refresh -> { R.id.refresh -> {
api.update().enqueue(object : Callback<String> { api.update().enqueue(object : Callback<String> {
override fun onResponse(call: Call<String>, response: Response<String>) { override fun onResponse(
Toast.makeText(this@HomeActivity, call: Call<String>,
R.string.refresh_success_response, Toast.LENGTH_LONG) response: Response<String>
) {
Toast.makeText(
this@HomeActivity,
R.string.refresh_success_response, Toast.LENGTH_LONG
)
.show() .show()
} }
override fun onFailure(call: Call<String>, t: Throwable) { override fun onFailure(call: Call<String>, t: Throwable) {
Toast.makeText(this@HomeActivity, R.string.refresh_failer_message, Toast.LENGTH_SHORT).show() Toast.makeText(
this@HomeActivity,
R.string.refresh_failer_message,
Toast.LENGTH_SHORT
).show()
} }
}) })
Toast.makeText(this, R.string.refresh_in_progress, Toast.LENGTH_SHORT).show() Toast.makeText(this, R.string.refresh_in_progress, Toast.LENGTH_SHORT).show()
@ -886,25 +1003,45 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
val ids = items.map { it.id } val ids = items.map { it.id }
api.readAll(ids).enqueue(object : Callback<SuccessResponse> { api.readAll(ids).enqueue(object : Callback<SuccessResponse> {
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) { override fun onResponse(
call: Call<SuccessResponse>,
response: Response<SuccessResponse>
) {
if (response.body() != null && response.body()!!.isSuccess) { if (response.body() != null && response.body()!!.isSuccess) {
Toast.makeText(this@HomeActivity, R.string.all_posts_read, Toast.LENGTH_SHORT).show() Toast.makeText(
this@HomeActivity,
R.string.all_posts_read,
Toast.LENGTH_SHORT
).show()
tabNewBadge.removeBadge() tabNewBadge.removeBadge()
} else {
Toast.makeText(
this@HomeActivity,
R.string.all_posts_not_read,
Toast.LENGTH_SHORT
).show()
} }
else
Toast.makeText(this@HomeActivity, R.string.all_posts_not_read, Toast.LENGTH_SHORT).show()
swipeRefreshLayout.isRefreshing = false swipeRefreshLayout.isRefreshing = false
} }
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) { override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
Toast.makeText(this@HomeActivity, R.string.all_posts_not_read, Toast.LENGTH_SHORT).show() Toast.makeText(
this@HomeActivity,
R.string.all_posts_not_read,
Toast.LENGTH_SHORT
).show()
swipeRefreshLayout.isRefreshing = false swipeRefreshLayout.isRefreshing = false
} }
}) })
items = ArrayList() items = ArrayList()
if (items.isEmpty()) if (items.isEmpty()) {
Toast.makeText(this@HomeActivity, R.string.nothing_here, Toast.LENGTH_SHORT).show() Toast.makeText(
this@HomeActivity,
R.string.nothing_here,
Toast.LENGTH_SHORT
).show()
}
handleListResult() handleListResult()
} }
return true return true
@ -937,10 +1074,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
} }
private fun maxItemNumber(): Int = private fun maxItemNumber(): Int =
when (elementsShown) { when (elementsShown) {
UNREAD_SHOWN -> badgeNew UNREAD_SHOWN -> badgeNew
READ_SHOWN -> badgeAll READ_SHOWN -> badgeAll
FAV_SHOWN -> badgeFavs FAV_SHOWN -> badgeFavs
else -> badgeNew // if !elementsShown then unread are fetched. else -> badgeNew // if !elementsShown then unread are fetched.
} }
} }

View File

@ -10,7 +10,6 @@ import android.preference.PreferenceManager
import android.support.v7.app.AppCompatDelegate import android.support.v7.app.AppCompatDelegate
import android.view.View import android.view.View
class IntroActivity : MaterialIntroActivity() { class IntroActivity : MaterialIntroActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -18,33 +17,44 @@ class IntroActivity : MaterialIntroActivity() {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true) AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
addSlide(SlideFragmentBuilder() addSlide(
.backgroundColor(R.color.colorPrimary) SlideFragmentBuilder()
.buttonsColor(R.color.colorAccent) .backgroundColor(R.color.colorPrimary)
.image(R.drawable.web_hi_res_512) .buttonsColor(R.color.colorAccent)
.title(getString(R.string.intro_hello_title)) .image(R.drawable.web_hi_res_512)
.description(getString(R.string.intro_hello_message)) .title(getString(R.string.intro_hello_title))
.build()) .description(getString(R.string.intro_hello_message))
.build()
)
addSlide(SlideFragmentBuilder() addSlide(
.backgroundColor(R.color.colorAccent) SlideFragmentBuilder()
.buttonsColor(R.color.colorPrimary) .backgroundColor(R.color.colorAccent)
.image(R.drawable.ic_info_outline_white_48px) .buttonsColor(R.color.colorPrimary)
.title(getString(R.string.intro_needs_selfoss_title)) .image(R.drawable.ic_info_outline_white_48px)
.description(getString(R.string.intro_needs_selfoss_message)) .title(getString(R.string.intro_needs_selfoss_title))
.build(), .description(getString(R.string.intro_needs_selfoss_message))
MessageButtonBehaviour(View.OnClickListener { .build(),
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://selfoss.aditu.de")) MessageButtonBehaviour(
startActivity(browserIntent) View.OnClickListener {
}, getString(R.string.intro_needs_selfoss_link))) val browserIntent = Intent(
Intent.ACTION_VIEW,
Uri.parse("https://selfoss.aditu.de")
)
startActivity(browserIntent)
}, getString(R.string.intro_needs_selfoss_link)
)
)
addSlide(SlideFragmentBuilder() addSlide(
.backgroundColor(R.color.colorPrimaryDark) SlideFragmentBuilder()
.buttonsColor(R.color.colorAccentDark) .backgroundColor(R.color.colorPrimaryDark)
.image(R.drawable.ic_thumb_up_white_48px) .buttonsColor(R.color.colorAccentDark)
.title(getString(R.string.intro_all_set_title)) .image(R.drawable.ic_thumb_up_white_48px)
.description(getString(R.string.intro_all_set_message)) .title(getString(R.string.intro_all_set_title))
.build()) .description(getString(R.string.intro_all_set_message))
.build()
)
} }
override fun onFinish() { override fun onFinish() {

View File

@ -8,7 +8,6 @@ import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import android.support.v7.app.AlertDialog import android.support.v7.app.AlertDialog
import android.support.v7.app.AppCompatActivity import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
import android.text.TextUtils import android.text.TextUtils
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
@ -25,12 +24,10 @@ import com.ftinc.scoop.Scoop
import com.google.firebase.analytics.FirebaseAnalytics import com.google.firebase.analytics.FirebaseAnalytics
import com.mikepenz.aboutlibraries.Libs import com.mikepenz.aboutlibraries.Libs
import com.mikepenz.aboutlibraries.LibsBuilder import com.mikepenz.aboutlibraries.LibsBuilder
import kotlinx.android.synthetic.main.activity_login.*
import retrofit2.Call import retrofit2.Call
import retrofit2.Callback import retrofit2.Callback
import retrofit2.Response import retrofit2.Response
import kotlinx.android.synthetic.main.activity_login.*
class LoginActivity : AppCompatActivity() { class LoginActivity : AppCompatActivity() {
@ -45,8 +42,6 @@ class LoginActivity : AppCompatActivity() {
private lateinit var userIdentifier: String private lateinit var userIdentifier: String
private var logErrors: Boolean = false private var logErrors: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
Scoop.getInstance().apply(this) Scoop.getInstance().apply(this)
@ -81,13 +76,15 @@ class LoginActivity : AppCompatActivity() {
warningText.visibility = visi warningText.visibility = visi
} }
passwordView.setOnEditorActionListener(TextView.OnEditorActionListener { _, id, _ -> passwordView.setOnEditorActionListener(
if (id == R.id.loginView || id == EditorInfo.IME_NULL) { TextView.OnEditorActionListener { _, id, _ ->
attemptLogin() if (id == R.id.loginView || id == EditorInfo.IME_NULL) {
return@OnEditorActionListener true attemptLogin()
} return@OnEditorActionListener true
false }
}) false
}
)
signInButton.setOnClickListener { attemptLogin() } signInButton.setOnClickListener { attemptLogin() }
@ -116,7 +113,8 @@ class LoginActivity : AppCompatActivity() {
alertDialog.setButton( alertDialog.setButton(
AlertDialog.BUTTON_NEUTRAL, AlertDialog.BUTTON_NEUTRAL,
"OK", "OK",
{ dialog, _ -> dialog.dismiss() }) { dialog, _ -> dialog.dismiss() }
)
alertDialog.show() alertDialog.show()
} }
} }
@ -158,7 +156,8 @@ class LoginActivity : AppCompatActivity() {
alertDialog.setButton( alertDialog.setButton(
AlertDialog.BUTTON_NEUTRAL, AlertDialog.BUTTON_NEUTRAL,
"OK", "OK",
{ dialog, _ -> dialog.dismiss() }) { dialog, _ -> dialog.dismiss() }
)
alertDialog.show() alertDialog.show()
inValidCount = 0 inValidCount = 0
} }
@ -191,7 +190,12 @@ class LoginActivity : AppCompatActivity() {
editor.putBoolean("isSelfSignedCert", isWithSelfSignedCert) editor.putBoolean("isSelfSignedCert", isWithSelfSignedCert)
editor.apply() editor.apply()
val api = SelfossApi(this, this@LoginActivity, isWithSelfSignedCert, isWithSelfSignedCert) val api = SelfossApi(
this,
this@LoginActivity,
isWithSelfSignedCert,
isWithSelfSignedCert
)
api.login().enqueue(object : Callback<SuccessResponse> { api.login().enqueue(object : Callback<SuccessResponse> {
private fun preferenceError(t: Throwable) { private fun preferenceError(t: Throwable) {
editor.remove("url") editor.remove("url")
@ -209,12 +213,19 @@ class LoginActivity : AppCompatActivity() {
Crashlytics.setUserIdentifier(userIdentifier) Crashlytics.setUserIdentifier(userIdentifier)
Crashlytics.log(100, "LOGIN_DEBUG_ERRROR", t.message) Crashlytics.log(100, "LOGIN_DEBUG_ERRROR", t.message)
Crashlytics.logException(t) Crashlytics.logException(t)
Toast.makeText(this@LoginActivity, t.message, Toast.LENGTH_LONG).show() Toast.makeText(
this@LoginActivity,
t.message,
Toast.LENGTH_LONG
).show()
} }
showProgress(false) showProgress(false)
} }
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) { override fun onResponse(
call: Call<SuccessResponse>,
response: Response<SuccessResponse>
) {
if (response.body() != null && response.body()!!.isSuccess) { if (response.body() != null && response.body()!!.isSuccess) {
firebaseAnalytics.logEvent(FirebaseAnalytics.Event.LOGIN, Bundle()) firebaseAnalytics.logEvent(FirebaseAnalytics.Event.LOGIN, Bundle())
goToMain() goToMain()
@ -230,7 +241,6 @@ class LoginActivity : AppCompatActivity() {
} }
} }
private fun showProgress(show: Boolean) { private fun showProgress(show: Boolean) {
val shortAnimTime = resources.getInteger(android.R.integer.config_shortAnimTime) val shortAnimTime = resources.getInteger(android.R.integer.config_shortAnimTime)
@ -241,10 +251,11 @@ class LoginActivity : AppCompatActivity() {
.alpha( .alpha(
if (show) 0F else 1F if (show) 0F else 1F
).setListener(object : AnimatorListenerAdapter() { ).setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) { override fun onAnimationEnd(animation: Animator) {
loginForm.visibility = if (show) View.GONE else View.VISIBLE loginForm.visibility = if (show) View.GONE else View.VISIBLE
} }
}) }
)
loginProgress.visibility = if (show) View.VISIBLE else View.GONE loginProgress.visibility = if (show) View.VISIBLE else View.GONE
loginProgress loginProgress
@ -253,10 +264,11 @@ class LoginActivity : AppCompatActivity() {
.alpha( .alpha(
if (show) 1F else 0F if (show) 1F else 0F
).setListener(object : AnimatorListenerAdapter() { ).setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) { override fun onAnimationEnd(animation: Animator) {
loginProgress.visibility = if (show) View.VISIBLE else View.GONE loginProgress.visibility = if (show) View.VISIBLE else View.GONE
} }
}) }
)
} }
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {

View File

@ -5,15 +5,16 @@ import android.os.Bundle
import android.preference.PreferenceManager import android.preference.PreferenceManager
import android.support.v7.app.AppCompatActivity import android.support.v7.app.AppCompatActivity
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)
if (PreferenceManager.getDefaultSharedPreferences(baseContext).getBoolean("firstStart", true)) { if (PreferenceManager.getDefaultSharedPreferences(baseContext).getBoolean(
"firstStart",
true
)) {
val i = Intent(this@MainActivity, IntroActivity::class.java) val i = Intent(this@MainActivity, IntroActivity::class.java)
startActivity(i) startActivity(i)
} else { } else {
@ -22,6 +23,5 @@ class MainActivity : AppCompatActivity() {
} }
finish() finish()
} }
} }

View File

@ -21,9 +21,6 @@ import io.fabric.sdk.android.Fabric
import java.io.IOException import java.io.IOException
import java.util.UUID.randomUUID import java.util.UUID.randomUUID
class MyApp : MultiDexApplication() { class MyApp : MultiDexApplication() {
override fun onCreate() { override fun onCreate() {
@ -46,14 +43,13 @@ class MyApp : MultiDexApplication() {
initTheme() initTheme()
tryToHandleBug() tryToHandleBug()
} }
private fun initAmplify() { private fun initAmplify() {
Amplify.initSharedInstance(this) Amplify.initSharedInstance(this)
.setPositiveFeedbackCollectors(GooglePlayStoreFeedbackCollector()) .setPositiveFeedbackCollectors(GooglePlayStoreFeedbackCollector())
.setCriticalFeedbackCollectors(DefaultEmailFeedbackCollector(BuildConfig.FEEDBACK_EMAIL)) .setCriticalFeedbackCollectors(DefaultEmailFeedbackCollector(BuildConfig.FEEDBACK_EMAIL))
.applyAllDefaultRules() .applyAllDefaultRules()
} }
private fun initCache() { private fun initCache() {
@ -66,12 +62,16 @@ class MyApp : MultiDexApplication() {
private fun initDrawerImageLoader() { private fun initDrawerImageLoader() {
DrawerImageLoader.init(object : AbstractDrawerImageLoader() { DrawerImageLoader.init(object : AbstractDrawerImageLoader() {
override fun set(imageView: ImageView?, uri: Uri?, placeholder: Drawable?, tag: String?) { override fun set(
imageView: ImageView?,
uri: Uri?,
placeholder: Drawable?,
tag: String?
) {
Glide.with(imageView?.context) Glide.with(imageView?.context)
.load(uri) .load(uri)
.apply(RequestOptions.fitCenterTransform() .apply(RequestOptions.fitCenterTransform().placeholder(placeholder))
.placeholder(placeholder)) .into(imageView)
.into(imageView)
} }
override fun cancel(imageView: ImageView?) { override fun cancel(imageView: ImageView?) {
@ -86,33 +86,35 @@ class MyApp : MultiDexApplication() {
private fun initTheme() { private fun initTheme() {
Scoop.waffleCone() Scoop.waffleCone()
.addFlavor(getString(R.string.default_theme), R.style.NoBar, true) .addFlavor(getString(R.string.default_theme), R.style.NoBar, true)
.addFlavor(getString(R.string.default_dark_theme), R.style.NoBarDark) .addFlavor(getString(R.string.default_dark_theme), R.style.NoBarDark)
.addFlavor(getString(R.string.teal_orange_theme), R.style.NoBarTealOrange) .addFlavor(getString(R.string.teal_orange_theme), R.style.NoBarTealOrange)
.addFlavor(getString(R.string.teal_orange_dark_theme), R.style.NoBarTealOrangeDark) .addFlavor(getString(R.string.teal_orange_dark_theme), R.style.NoBarTealOrangeDark)
.addFlavor(getString(R.string.cyan_pink_theme), R.style.NoBarCyanPink) .addFlavor(getString(R.string.cyan_pink_theme), R.style.NoBarCyanPink)
.addFlavor(getString(R.string.cyan_pink_dark_theme), R.style.NoBarCyanPinkDark) .addFlavor(getString(R.string.cyan_pink_dark_theme), R.style.NoBarCyanPinkDark)
.addFlavor(getString(R.string.grey_orange_theme), R.style.NoBarGreyOrange) .addFlavor(getString(R.string.grey_orange_theme), R.style.NoBarGreyOrange)
.addFlavor(getString(R.string.grey_orange_dark_theme), R.style.NoBarGreyOrangeDark) .addFlavor(getString(R.string.grey_orange_dark_theme), R.style.NoBarGreyOrangeDark)
.addFlavor(getString(R.string.blue_amber_theme), R.style.NoBarBlueAmber) .addFlavor(getString(R.string.blue_amber_theme), R.style.NoBarBlueAmber)
.addFlavor(getString(R.string.blue_amber_dark_theme), R.style.NoBarBlueAmberDark) .addFlavor(getString(R.string.blue_amber_dark_theme), R.style.NoBarBlueAmberDark)
.addFlavor(getString(R.string.indigo_pink_theme), R.style.NoBarIndigoPink) .addFlavor(getString(R.string.indigo_pink_theme), R.style.NoBarIndigoPink)
.addFlavor(getString(R.string.indigo_pink_dark_theme), R.style.NoBarIndigoPinkDark) .addFlavor(getString(R.string.indigo_pink_dark_theme), R.style.NoBarIndigoPinkDark)
.addFlavor(getString(R.string.red_teal_theme), R.style.NoBarRedTeal) .addFlavor(getString(R.string.red_teal_theme), R.style.NoBarRedTeal)
.addFlavor(getString(R.string.red_teal_dark_theme), R.style.NoBarRedTealDark) .addFlavor(getString(R.string.red_teal_dark_theme), R.style.NoBarRedTealDark)
.setSharedPreferences(PreferenceManager.getDefaultSharedPreferences(this)) .setSharedPreferences(PreferenceManager.getDefaultSharedPreferences(this))
.initialize() .initialize()
} }
private fun tryToHandleBug() { private fun tryToHandleBug() {
val oldHandler = Thread.getDefaultUncaughtExceptionHandler() val oldHandler = Thread.getDefaultUncaughtExceptionHandler()
Thread.setDefaultUncaughtExceptionHandler { thread, e -> Thread.setDefaultUncaughtExceptionHandler { thread, e ->
if (e is java.lang.NoClassDefFoundError && e.stackTrace.asList().any { it.toString().contains("android.view.ViewDebug") }) if (e is java.lang.NoClassDefFoundError && e.stackTrace.asList().any {
it.toString().contains("android.view.ViewDebug")
}) {
Unit Unit
else } else {
oldHandler.uncaughtException(thread, e) oldHandler.uncaughtException(thread, e)
}
} }
} }
} }

View File

@ -23,12 +23,11 @@ import com.bumptech.glide.request.RequestOptions
import com.crashlytics.android.Crashlytics import com.crashlytics.android.Crashlytics
import com.ftinc.scoop.Scoop import com.ftinc.scoop.Scoop
import com.github.rubensousa.floatingtoolbar.FloatingToolbar import com.github.rubensousa.floatingtoolbar.FloatingToolbar
import kotlinx.android.synthetic.main.activity_reader.*
import org.sufficientlysecure.htmltextview.HtmlHttpImageGetter import org.sufficientlysecure.htmltextview.HtmlHttpImageGetter
import retrofit2.Call import retrofit2.Call
import retrofit2.Callback import retrofit2.Callback
import retrofit2.Response import retrofit2.Response
import kotlinx.android.synthetic.main.activity_reader.*
class ReaderActivity : AppCompatActivity() { class ReaderActivity : AppCompatActivity() {
private lateinit var mCustomTabActivityHelper: CustomTabActivityHelper private lateinit var mCustomTabActivityHelper: CustomTabActivityHelper
@ -40,7 +39,6 @@ class ReaderActivity : AppCompatActivity() {
private lateinit var contentTitle: String private lateinit var contentTitle: String
private lateinit var fab: FloatingActionButton private lateinit var fab: FloatingActionButton
override fun onStop() { override fun onStop() {
super.onStop() super.onStop()
mCustomTabActivityHelper.unbindCustomTabsService(this) mCustomTabActivityHelper.unbindCustomTabsService(this)
@ -81,7 +79,8 @@ class ReaderActivity : AppCompatActivity() {
customTabsIntent, customTabsIntent,
false, false,
false, false,
this@ReaderActivity) this@ReaderActivity
)
else -> Unit else -> Unit
} }
} }
@ -111,23 +110,34 @@ class ReaderActivity : AppCompatActivity() {
} }
} }
nestedScrollView.setOnScrollChangeListener(NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY -> nestedScrollView.setOnScrollChangeListener(
if (scrollY > oldScrollY) { NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
fab.hide() if (scrollY > oldScrollY) {
} else { fab.hide()
if (mFloatingToolbar.isShowing) mFloatingToolbar.hide() else fab.show() } else {
} if (mFloatingToolbar.isShowing) mFloatingToolbar.hide() else fab.show()
}) }
}
)
content.movementMethod = LinkMovementMethod.getInstance() content.movementMethod = LinkMovementMethod.getInstance()
} }
private fun getContentFromMercury(customTabsIntent: CustomTabsIntent, prefs: SharedPreferences) { private fun getContentFromMercury(
customTabsIntent: CustomTabsIntent,
prefs: SharedPreferences
) {
progressBar.visibility = View.VISIBLE progressBar.visibility = View.VISIBLE
val parser = MercuryApi(BuildConfig.MERCURY_KEY, prefs.getBoolean("should_log_everything", false)) val parser = MercuryApi(
BuildConfig.MERCURY_KEY,
prefs.getBoolean("should_log_everything", false)
)
parser.parseUrl(url).enqueue(object : Callback<ParsedContent> { parser.parseUrl(url).enqueue(object : Callback<ParsedContent> {
override fun onResponse(call: Call<ParsedContent>, response: Response<ParsedContent>) { override fun onResponse(
call: Call<ParsedContent>,
response: Response<ParsedContent>
) {
if (response.body() != null && response.body()!!.content != null && response.body()!!.content.isNotEmpty()) { if (response.body() != null && response.body()!!.content != null && response.body()!!.content.isNotEmpty()) {
source.text = response.body()!!.domain source.text = response.body()!!.domain
titleView.text = response.body()!!.title titleView.text = response.body()!!.title
@ -152,14 +162,23 @@ class ReaderActivity : AppCompatActivity() {
nestedScrollView.scrollTo(0, 0) nestedScrollView.scrollTo(0, 0)
progressBar.visibility = View.GONE progressBar.visibility = View.GONE
} else openInBrowserAfterFailing(customTabsIntent) } else {
openInBrowserAfterFailing(customTabsIntent)
}
} }
override fun onFailure(call: Call<ParsedContent>, t: Throwable) = openInBrowserAfterFailing(customTabsIntent) override fun onFailure(
call: Call<ParsedContent>,
t: Throwable
) = openInBrowserAfterFailing(customTabsIntent)
}) })
} }
private fun tryToHandleHtml(c: String, customTabsIntent: CustomTabsIntent, prefs: SharedPreferences) { private fun tryToHandleHtml(
c: String,
customTabsIntent: CustomTabsIntent,
prefs: SharedPreferences
) {
try { try {
content.text = Html.fromHtml(c, HtmlHttpImageGetter(content, null, true), null) content.text = Html.fromHtml(c, HtmlHttpImageGetter(content, null, true), null)

View File

@ -10,14 +10,13 @@ import apps.amine.bou.readerforselfoss.adapters.SourcesListAdapter
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
import apps.amine.bou.readerforselfoss.api.selfoss.Sources import apps.amine.bou.readerforselfoss.api.selfoss.Sources
import com.ftinc.scoop.Scoop import com.ftinc.scoop.Scoop
import kotlinx.android.synthetic.main.activity_sources.*
import retrofit2.Call import retrofit2.Call
import retrofit2.Callback import retrofit2.Callback
import retrofit2.Response import retrofit2.Response
import kotlinx.android.synthetic.main.activity_sources.*
class SourcesActivity : AppCompatActivity() { class SourcesActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
Scoop.getInstance().apply(this) Scoop.getInstance().apply(this)
@ -39,25 +38,43 @@ class SourcesActivity : AppCompatActivity() {
val prefs = PreferenceManager.getDefaultSharedPreferences(this) val prefs = PreferenceManager.getDefaultSharedPreferences(this)
val api = SelfossApi(this, this@SourcesActivity, prefs.getBoolean("isSelfSignedCert", false), prefs.getBoolean("should_log_everything", false)) val api = SelfossApi(
this,
this@SourcesActivity,
prefs.getBoolean("isSelfSignedCert", false),
prefs.getBoolean("should_log_everything", false)
)
var items: ArrayList<Sources> = ArrayList() var items: ArrayList<Sources> = ArrayList()
recyclerView.setHasFixedSize(true) recyclerView.setHasFixedSize(true)
recyclerView.layoutManager = mLayoutManager recyclerView.layoutManager = mLayoutManager
api.sources.enqueue(object : Callback<List<Sources>> { api.sources.enqueue(object : Callback<List<Sources>> {
override fun onResponse(call: Call<List<Sources>>, response: Response<List<Sources>>) { override fun onResponse(
call: Call<List<Sources>>,
response: Response<List<Sources>>
) {
if (response.body() != null && response.body()!!.isNotEmpty()) { if (response.body() != null && response.body()!!.isNotEmpty()) {
items = response.body() as ArrayList<Sources> items = response.body() as ArrayList<Sources>
} }
val mAdapter = SourcesListAdapter(this@SourcesActivity, items, api) val mAdapter = SourcesListAdapter(this@SourcesActivity, items, api)
recyclerView.adapter = mAdapter recyclerView.adapter = mAdapter
mAdapter.notifyDataSetChanged() mAdapter.notifyDataSetChanged()
if (items.isEmpty()) Toast.makeText(this@SourcesActivity, R.string.nothing_here, Toast.LENGTH_SHORT).show() if (items.isEmpty()) {
Toast.makeText(
this@SourcesActivity,
R.string.nothing_here,
Toast.LENGTH_SHORT
).show()
}
} }
override fun onFailure(call: Call<List<Sources>>, t: Throwable) { override fun onFailure(call: Call<List<Sources>>, t: Throwable) {
Toast.makeText(this@SourcesActivity, R.string.cant_get_sources, Toast.LENGTH_SHORT).show() Toast.makeText(
this@SourcesActivity,
R.string.cant_get_sources,
Toast.LENGTH_SHORT
).show()
} }
}) })

View File

@ -33,21 +33,23 @@ import com.bumptech.glide.Glide
import com.crashlytics.android.Crashlytics import com.crashlytics.android.Crashlytics
import com.like.LikeButton import com.like.LikeButton
import com.like.OnLikeListener import com.like.OnLikeListener
import kotlinx.android.synthetic.main.card_item.view.*
import retrofit2.Call import retrofit2.Call
import retrofit2.Callback import retrofit2.Callback
import retrofit2.Response import retrofit2.Response
import kotlinx.android.synthetic.main.card_item.view.*
class ItemCardAdapter(private val app: Activity, class ItemCardAdapter(
private val items: ArrayList<Item>, private val app: Activity,
private val api: SelfossApi, private val items: ArrayList<Item>,
private val helper: CustomTabActivityHelper, private val api: SelfossApi,
private val internalBrowser: Boolean, private val helper: CustomTabActivityHelper,
private val articleViewer: Boolean, private val internalBrowser: Boolean,
private val fullHeightCards: Boolean, private val articleViewer: Boolean,
private val appColors: AppColors, private val fullHeightCards: Boolean,
val debugReadingItems: Boolean, private val appColors: AppColors,
val userIdentifier: String) : RecyclerView.Adapter<ItemCardAdapter.ViewHolder>() { val debugReadingItems: Boolean,
val userIdentifier: String
) : RecyclerView.Adapter<ItemCardAdapter.ViewHolder>() {
private val c: Context = app.baseContext private val c: Context = app.baseContext
private val generator: ColorGenerator = ColorGenerator.MATERIAL private val generator: ColorGenerator = ColorGenerator.MATERIAL
@ -94,13 +96,21 @@ class ItemCardAdapter(private val app: Activity,
private fun doUnmark(i: Item, position: Int) { private fun doUnmark(i: Item, position: Int) {
val s = Snackbar val s = Snackbar
.make(app.findViewById(R.id.coordLayout), R.string.marked_as_read, Snackbar.LENGTH_LONG) .make(
app.findViewById(R.id.coordLayout),
R.string.marked_as_read,
Snackbar.LENGTH_LONG
)
.setAction(R.string.undo_string) { .setAction(R.string.undo_string) {
items.add(position, i) items.add(position, i)
notifyItemInserted(position) notifyItemInserted(position)
api.unmarkItem(i.id).enqueue(object : Callback<SuccessResponse> { api.unmarkItem(i.id).enqueue(object : Callback<SuccessResponse> {
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {} override fun onResponse(
call: Call<SuccessResponse>,
response: Response<SuccessResponse>
) {
}
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) { override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
items.remove(i) items.remove(i)
@ -124,7 +134,10 @@ class ItemCardAdapter(private val app: Activity,
notifyItemRemoved(position) notifyItemRemoved(position)
api.markItem(i.id).enqueue(object : Callback<SuccessResponse> { api.markItem(i.id).enqueue(object : Callback<SuccessResponse> {
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) { override fun onResponse(
call: Call<SuccessResponse>,
response: Response<SuccessResponse>
) {
if (!response.succeeded() && debugReadingItems) { if (!response.succeeded() && debugReadingItems) {
val message = val message =
"message: ${response.message()} " + "message: ${response.message()} " +
@ -150,12 +163,15 @@ class ItemCardAdapter(private val app: Activity,
Crashlytics.logException(t) Crashlytics.logException(t)
Toast.makeText(c, t.message, Toast.LENGTH_LONG).show() Toast.makeText(c, t.message, Toast.LENGTH_LONG).show()
} }
Toast.makeText(app, app.getString(R.string.cant_mark_read), Toast.LENGTH_SHORT).show() Toast.makeText(
app,
app.getString(R.string.cant_mark_read),
Toast.LENGTH_SHORT
).show()
items.add(i) items.add(i)
notifyItemInserted(position) notifyItemInserted(position)
} }
}) })
} }
inner class ViewHolder(val mView: CardView) : RecyclerView.ViewHolder(mView) { inner class ViewHolder(val mView: CardView) : RecyclerView.ViewHolder(mView) {
@ -176,11 +192,22 @@ class ItemCardAdapter(private val app: Activity,
override fun liked(likeButton: LikeButton) { override fun liked(likeButton: LikeButton) {
val (id) = items[adapterPosition] val (id) = items[adapterPosition]
api.starrItem(id).enqueue(object : Callback<SuccessResponse> { api.starrItem(id).enqueue(object : Callback<SuccessResponse> {
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {} override fun onResponse(
call: Call<SuccessResponse>,
response: Response<SuccessResponse>
) {
}
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) { override fun onFailure(
call: Call<SuccessResponse>,
t: Throwable
) {
mView.favButton.isLiked = false mView.favButton.isLiked = false
Toast.makeText(c, R.string.cant_mark_favortie, Toast.LENGTH_SHORT).show() Toast.makeText(
c,
R.string.cant_mark_favortie,
Toast.LENGTH_SHORT
).show()
} }
}) })
} }
@ -188,11 +215,22 @@ class ItemCardAdapter(private val app: Activity,
override fun unLiked(likeButton: LikeButton) { override fun unLiked(likeButton: LikeButton) {
val (id) = items[adapterPosition] val (id) = items[adapterPosition]
api.unstarrItem(id).enqueue(object : Callback<SuccessResponse> { api.unstarrItem(id).enqueue(object : Callback<SuccessResponse> {
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {} override fun onResponse(
call: Call<SuccessResponse>,
response: Response<SuccessResponse>
) {
}
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) { override fun onFailure(
call: Call<SuccessResponse>,
t: Throwable
) {
mView.favButton.isLiked = true mView.favButton.isLiked = true
Toast.makeText(c, R.string.cant_unmark_favortie, Toast.LENGTH_SHORT).show() Toast.makeText(
c,
R.string.cant_unmark_favortie,
Toast.LENGTH_SHORT
).show()
} }
}) })
} }
@ -212,7 +250,8 @@ class ItemCardAdapter(private val app: Activity,
helper.bindCustomTabsService(app) helper.bindCustomTabsService(app)
mView.setOnClickListener { mView.setOnClickListener {
c.openItemUrl(items[adapterPosition].getLinkDecoded(), c.openItemUrl(
items[adapterPosition].getLinkDecoded(),
items[adapterPosition].content, items[adapterPosition].content,
items[adapterPosition].getThumbnail(c), items[adapterPosition].getThumbnail(c),
items[adapterPosition].title, items[adapterPosition].title,
@ -220,7 +259,8 @@ class ItemCardAdapter(private val app: Activity,
customTabsIntent, customTabsIntent,
internalBrowser, internalBrowser,
articleViewer, articleViewer,
app) app
)
} }
} }
} }

View File

@ -1,6 +1,5 @@
package apps.amine.bou.readerforselfoss.adapters package apps.amine.bou.readerforselfoss.adapters
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.graphics.Color import android.graphics.Color
@ -32,28 +31,34 @@ import com.amulyakhare.textdrawable.util.ColorGenerator
import com.crashlytics.android.Crashlytics import com.crashlytics.android.Crashlytics
import com.like.LikeButton import com.like.LikeButton
import com.like.OnLikeListener import com.like.OnLikeListener
import kotlinx.android.synthetic.main.list_item.view.*
import retrofit2.Call import retrofit2.Call
import retrofit2.Callback import retrofit2.Callback
import retrofit2.Response import retrofit2.Response
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
import kotlinx.android.synthetic.main.list_item.view.*
class ItemListAdapter(private val app: Activity, class ItemListAdapter(
private val items: ArrayList<Item>, private val app: Activity,
private val api: SelfossApi, private val items: ArrayList<Item>,
private val helper: CustomTabActivityHelper, private val api: SelfossApi,
private val clickBehavior: Boolean, private val helper: CustomTabActivityHelper,
private val internalBrowser: Boolean, private val clickBehavior: Boolean,
private val articleViewer: Boolean, private val internalBrowser: Boolean,
val debugReadingItems: Boolean, private val articleViewer: Boolean,
val userIdentifier: String) : RecyclerView.Adapter<ItemListAdapter.ViewHolder>() { val debugReadingItems: Boolean,
val userIdentifier: String
) : RecyclerView.Adapter<ItemListAdapter.ViewHolder>() {
private val generator: ColorGenerator = ColorGenerator.MATERIAL private val generator: ColorGenerator = ColorGenerator.MATERIAL
private val c: Context = app.baseContext private val c: Context = app.baseContext
private val bars: ArrayList<Boolean> = ArrayList(Collections.nCopies(items.size + 1, false)) private val bars: ArrayList<Boolean> = ArrayList(Collections.nCopies(items.size + 1, false))
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(c).inflate(R.layout.list_item, parent, false) as ConstraintLayout val v = LayoutInflater.from(c).inflate(
R.layout.list_item,
parent,
false
) as ConstraintLayout
return ViewHolder(v) return ViewHolder(v)
} }
@ -70,12 +75,14 @@ class ItemListAdapter(private val app: Activity,
val sizeInInt = 46 val sizeInInt = 46
val sizeInDp = TypedValue.applyDimension( val sizeInDp = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, sizeInInt.toFloat(), c.resources TypedValue.COMPLEX_UNIT_DIP, sizeInInt.toFloat(), c.resources
.displayMetrics).toInt() .displayMetrics
).toInt()
val marginInInt = 16 val marginInInt = 16
val marginInDp = TypedValue.applyDimension( val marginInDp = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, marginInInt.toFloat(), c.resources TypedValue.COMPLEX_UNIT_DIP, marginInInt.toFloat(), c.resources
.displayMetrics).toInt() .displayMetrics
).toInt()
val params = holder.mView.itemImage.layoutParams as ViewGroup.MarginLayoutParams val params = holder.mView.itemImage.layoutParams as ViewGroup.MarginLayoutParams
params.height = sizeInDp params.height = sizeInDp
@ -101,23 +108,34 @@ class ItemListAdapter(private val app: Activity,
c.bitmapCenterCrop(itm.getThumbnail(c), holder.mView.itemImage) c.bitmapCenterCrop(itm.getThumbnail(c), holder.mView.itemImage)
} }
if (bars[position]) holder.mView.actionBar.visibility = View.VISIBLE else holder.mView.actionBar.visibility = View.GONE if (bars[position]) {
holder.mView.actionBar.visibility = View.VISIBLE
} else {
holder.mView.actionBar.visibility = View.GONE
}
holder.mView.favButton.isLiked = itm.starred holder.mView.favButton.isLiked = itm.starred
} }
override fun getItemCount(): Int = items.size override fun getItemCount(): Int = items.size
private fun doUnmark(i: Item, position: Int) { private fun doUnmark(i: Item, position: Int) {
val s = Snackbar val s = Snackbar
.make(app.findViewById(R.id.coordLayout), R.string.marked_as_read, Snackbar.LENGTH_LONG) .make(
app.findViewById(R.id.coordLayout),
R.string.marked_as_read,
Snackbar.LENGTH_LONG
)
.setAction(R.string.undo_string) { .setAction(R.string.undo_string) {
items.add(position, i) items.add(position, i)
notifyItemInserted(position) notifyItemInserted(position)
api.unmarkItem(i.id).enqueue(object : Callback<SuccessResponse> { api.unmarkItem(i.id).enqueue(object : Callback<SuccessResponse> {
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {} override fun onResponse(
call: Call<SuccessResponse>,
response: Response<SuccessResponse>
) {
}
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) { override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
items.remove(i) items.remove(i)
@ -141,7 +159,10 @@ class ItemListAdapter(private val app: Activity,
notifyItemRemoved(position) notifyItemRemoved(position)
api.markItem(i.id).enqueue(object : Callback<SuccessResponse> { api.markItem(i.id).enqueue(object : Callback<SuccessResponse> {
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) { override fun onResponse(
call: Call<SuccessResponse>,
response: Response<SuccessResponse>
) {
if (!response.succeeded() && debugReadingItems) { if (!response.succeeded() && debugReadingItems) {
val message = val message =
"message: ${response.message()} " + "message: ${response.message()} " +
@ -157,7 +178,6 @@ class ItemListAdapter(private val app: Activity,
Toast.makeText(c, message, Toast.LENGTH_LONG).show() Toast.makeText(c, message, Toast.LENGTH_LONG).show()
} }
doUnmark(i, position) doUnmark(i, position)
} }
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) { override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
@ -167,12 +187,15 @@ class ItemListAdapter(private val app: Activity,
Crashlytics.logException(t) Crashlytics.logException(t)
Toast.makeText(c, t.message, Toast.LENGTH_LONG).show() Toast.makeText(c, t.message, Toast.LENGTH_LONG).show()
} }
Toast.makeText(app, app.getString(R.string.cant_mark_read), Toast.LENGTH_SHORT).show() Toast.makeText(
app,
app.getString(R.string.cant_mark_read),
Toast.LENGTH_SHORT
).show()
items.add(i) items.add(i)
notifyItemInserted(position) notifyItemInserted(position)
} }
}) })
} }
inner class ViewHolder(val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) { inner class ViewHolder(val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) {
@ -188,11 +211,22 @@ class ItemListAdapter(private val app: Activity,
override fun liked(likeButton: LikeButton) { override fun liked(likeButton: LikeButton) {
val (id) = items[adapterPosition] val (id) = items[adapterPosition]
api.starrItem(id).enqueue(object : Callback<SuccessResponse> { api.starrItem(id).enqueue(object : Callback<SuccessResponse> {
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {} override fun onResponse(
call: Call<SuccessResponse>,
response: Response<SuccessResponse>
) {
}
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) { override fun onFailure(
call: Call<SuccessResponse>,
t: Throwable
) {
mView.favButton.isLiked = false mView.favButton.isLiked = false
Toast.makeText(c, R.string.cant_mark_favortie, Toast.LENGTH_SHORT).show() Toast.makeText(
c,
R.string.cant_mark_favortie,
Toast.LENGTH_SHORT
).show()
} }
}) })
} }
@ -200,11 +234,22 @@ class ItemListAdapter(private val app: Activity,
override fun unLiked(likeButton: LikeButton) { override fun unLiked(likeButton: LikeButton) {
val (id) = items[adapterPosition] val (id) = items[adapterPosition]
api.unstarrItem(id).enqueue(object : Callback<SuccessResponse> { api.unstarrItem(id).enqueue(object : Callback<SuccessResponse> {
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {} override fun onResponse(
call: Call<SuccessResponse>,
response: Response<SuccessResponse>
) {
}
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) { override fun onFailure(
call: Call<SuccessResponse>,
t: Throwable
) {
mView.favButton.isLiked = true mView.favButton.isLiked = true
Toast.makeText(c, R.string.cant_unmark_favortie, Toast.LENGTH_SHORT).show() Toast.makeText(
c,
R.string.cant_unmark_favortie,
Toast.LENGTH_SHORT
).show()
} }
}) })
} }
@ -220,7 +265,6 @@ class ItemListAdapter(private val app: Activity,
} }
} }
private fun handleCustomTabActions() { private fun handleCustomTabActions() {
val customTabsIntent = c.buildCustomTabsIntent() val customTabsIntent = c.buildCustomTabsIntent()
helper.bindCustomTabsService(app) helper.bindCustomTabsService(app)
@ -228,7 +272,8 @@ class ItemListAdapter(private val app: Activity,
if (!clickBehavior) { if (!clickBehavior) {
mView.setOnClickListener { mView.setOnClickListener {
c.openItemUrl(items[adapterPosition].getLinkDecoded(), c.openItemUrl(
items[adapterPosition].getLinkDecoded(),
items[adapterPosition].content, items[adapterPosition].content,
items[adapterPosition].getThumbnail(c), items[adapterPosition].getThumbnail(c),
items[adapterPosition].title, items[adapterPosition].title,
@ -236,7 +281,8 @@ class ItemListAdapter(private val app: Activity,
customTabsIntent, customTabsIntent,
internalBrowser, internalBrowser,
articleViewer, articleViewer,
app) app
)
} }
mView.setOnLongClickListener { mView.setOnLongClickListener {
actionBarShowHide() actionBarShowHide()
@ -245,7 +291,8 @@ class ItemListAdapter(private val app: Activity,
} else { } else {
mView.setOnClickListener { actionBarShowHide() } mView.setOnClickListener { actionBarShowHide() }
mView.setOnLongClickListener { mView.setOnLongClickListener {
c.openItemUrl(items[adapterPosition].getLinkDecoded(), c.openItemUrl(
items[adapterPosition].getLinkDecoded(),
items[adapterPosition].content, items[adapterPosition].content,
items[adapterPosition].getThumbnail(c), items[adapterPosition].getThumbnail(c),
items[adapterPosition].title, items[adapterPosition].title,
@ -253,7 +300,8 @@ class ItemListAdapter(private val app: Activity,
customTabsIntent, customTabsIntent,
internalBrowser, internalBrowser,
articleViewer, articleViewer,
app) app
)
true true
} }
} }

View File

@ -7,8 +7,6 @@ import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Button import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import apps.amine.bou.readerforselfoss.R import apps.amine.bou.readerforselfoss.R
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
@ -18,20 +16,25 @@ import apps.amine.bou.readerforselfoss.utils.glide.circularBitmapDrawable
import apps.amine.bou.readerforselfoss.utils.toTextDrawableString import apps.amine.bou.readerforselfoss.utils.toTextDrawableString
import com.amulyakhare.textdrawable.TextDrawable import com.amulyakhare.textdrawable.TextDrawable
import com.amulyakhare.textdrawable.util.ColorGenerator import com.amulyakhare.textdrawable.util.ColorGenerator
import kotlinx.android.synthetic.main.source_list_item.view.*
import retrofit2.Call import retrofit2.Call
import retrofit2.Callback import retrofit2.Callback
import retrofit2.Response import retrofit2.Response
import kotlinx.android.synthetic.main.source_list_item.view.*
class SourcesListAdapter(
class SourcesListAdapter(private val app: Activity, private val app: Activity,
private val items: ArrayList<Sources>, private val items: ArrayList<Sources>,
private val api: SelfossApi) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>() { private val api: SelfossApi
) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>() {
private val c: Context = app.baseContext private val c: Context = app.baseContext
private val generator: ColorGenerator = ColorGenerator.MATERIAL private val generator: ColorGenerator = ColorGenerator.MATERIAL
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(c).inflate(R.layout.source_list_item, parent, false) as ConstraintLayout val v = LayoutInflater.from(c).inflate(
R.layout.source_list_item,
parent,
false
) as ConstraintLayout
return ViewHolder(v) return ViewHolder(v)
} }
@ -46,21 +49,17 @@ class SourcesListAdapter(private val app: Activity,
.builder() .builder()
.round() .round()
.build(itm.title.toTextDrawableString(), color) .build(itm.title.toTextDrawableString(), color)
holder.itemImage.setImageDrawable(drawable) holder.mView.itemImage.setImageDrawable(drawable)
} else { } else {
c.circularBitmapDrawable(itm.getIcon(c), holder.itemImage) c.circularBitmapDrawable(itm.getIcon(c), holder.mView.itemImage)
} }
holder.sourceTitle.text = itm.title holder.mView.sourceTitle.text = itm.title
} }
override fun getItemCount(): Int { override fun getItemCount(): Int = items.size
return items.size
}
inner class ViewHolder(internal val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) { inner class ViewHolder(internal val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) {
lateinit var itemImage: ImageView
lateinit var sourceTitle: TextView
init { init {
handleClickListeners() handleClickListeners()
@ -73,23 +72,32 @@ class SourcesListAdapter(private val app: Activity,
deleteBtn.setOnClickListener { deleteBtn.setOnClickListener {
val (id) = items[adapterPosition] val (id) = items[adapterPosition]
api.deleteSource(id).enqueue(object : Callback<SuccessResponse> { api.deleteSource(id).enqueue(object : Callback<SuccessResponse> {
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) { override fun onResponse(
call: Call<SuccessResponse>,
response: Response<SuccessResponse>
) {
if (response.body() != null && response.body()!!.isSuccess) { if (response.body() != null && response.body()!!.isSuccess) {
items.removeAt(adapterPosition) items.removeAt(adapterPosition)
notifyItemRemoved(adapterPosition) notifyItemRemoved(adapterPosition)
notifyItemRangeChanged(adapterPosition, itemCount) notifyItemRangeChanged(adapterPosition, itemCount)
} else { } else {
Toast.makeText(app, R.string.can_delete_source, Toast.LENGTH_SHORT).show() Toast.makeText(
app,
R.string.can_delete_source,
Toast.LENGTH_SHORT
).show()
} }
} }
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) { override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
Toast.makeText(app, R.string.can_delete_source, Toast.LENGTH_SHORT).show() Toast.makeText(
app,
R.string.can_delete_source,
Toast.LENGTH_SHORT
).show()
} }
}) })
} }
} }
} }
} }

View File

@ -7,30 +7,29 @@ import retrofit2.Call
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory import retrofit2.converter.gson.GsonConverterFactory
class MercuryApi(private val key: String, shouldLog: Boolean) { class MercuryApi(private val key: String, shouldLog: Boolean) {
private val service: MercuryService private val service: MercuryService
init { init {
val interceptor = HttpLoggingInterceptor() val interceptor = HttpLoggingInterceptor()
interceptor.level = if (shouldLog) interceptor.level = if (shouldLog) {
HttpLoggingInterceptor.Level.BODY HttpLoggingInterceptor.Level.BODY
else } else {
HttpLoggingInterceptor.Level.NONE HttpLoggingInterceptor.Level.NONE
}
val client = OkHttpClient.Builder().addInterceptor(interceptor).build() val client = OkHttpClient.Builder().addInterceptor(interceptor).build()
val gson = GsonBuilder() val gson = GsonBuilder()
.setLenient() .setLenient()
.create() .create()
val retrofit = val retrofit =
Retrofit Retrofit
.Builder() .Builder()
.baseUrl("https://mercury.postlight.com") .baseUrl("https://mercury.postlight.com")
.client(client) .client(client)
.addConverterFactory(GsonConverterFactory.create(gson)) .addConverterFactory(GsonConverterFactory.create(gson))
.build() .build()
service = retrofit.create(MercuryService::class.java) service = retrofit.create(MercuryService::class.java)
} }

View File

@ -4,38 +4,40 @@ import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
class ParsedContent(
class ParsedContent(@SerializedName("title") val title: String, @SerializedName("title") val title: String,
@SerializedName("content") val content: String, @SerializedName("content") val content: String,
@SerializedName("date_published") val date_published: String, @SerializedName("date_published") val date_published: String,
@SerializedName("lead_image_url") val lead_image_url: String, @SerializedName("lead_image_url") val lead_image_url: String,
@SerializedName("dek") val dek: String, @SerializedName("dek") val dek: String,
@SerializedName("url") val url: String, @SerializedName("url") val url: String,
@SerializedName("domain") val domain: String, @SerializedName("domain") val domain: String,
@SerializedName("excerpt") val excerpt: String, @SerializedName("excerpt") val excerpt: String,
@SerializedName("total_pages") val total_pages: Int, @SerializedName("total_pages") val total_pages: Int,
@SerializedName("rendered_pages") val rendered_pages: Int, @SerializedName("rendered_pages") val rendered_pages: Int,
@SerializedName("next_page_url") val next_page_url: String) : Parcelable { @SerializedName("next_page_url") val next_page_url: String
) : Parcelable {
companion object { companion object {
@JvmField val CREATOR: Parcelable.Creator<ParsedContent> = object : Parcelable.Creator<ParsedContent> { @JvmField
val CREATOR: Parcelable.Creator<ParsedContent> = object : Parcelable.Creator<ParsedContent> {
override fun createFromParcel(source: Parcel): ParsedContent = ParsedContent(source) override fun createFromParcel(source: Parcel): ParsedContent = ParsedContent(source)
override fun newArray(size: Int): Array<ParsedContent?> = arrayOfNulls(size) override fun newArray(size: Int): Array<ParsedContent?> = arrayOfNulls(size)
} }
} }
constructor(source: Parcel) : this( constructor(source: Parcel) : this(
title = source.readString(), title = source.readString(),
content = source.readString(), content = source.readString(),
date_published = source.readString(), date_published = source.readString(),
lead_image_url = source.readString(), lead_image_url = source.readString(),
dek = source.readString(), dek = source.readString(),
url = source.readString(), url = source.readString(),
domain = source.readString(), domain = source.readString(),
excerpt = source.readString(), excerpt = source.readString(),
total_pages = source.readInt(), total_pages = source.readInt(),
rendered_pages = source.readInt(), rendered_pages = source.readInt(),
next_page_url = source.readString() next_page_url = source.readString()
) )
override fun describeContents() = 0 override fun describeContents() = 0

View File

@ -1,13 +1,10 @@
package apps.amine.bou.readerforselfoss.api.mercury package apps.amine.bou.readerforselfoss.api.mercury
import retrofit2.Call import retrofit2.Call
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Header import retrofit2.http.Header
import retrofit2.http.Query import retrofit2.http.Query
interface MercuryService { interface MercuryService {
@GET("parser") @GET("parser")
fun parseUrl(@Query("url") url: String, @Header("x-api-key") key: String): Call<ParsedContent> fun parseUrl(@Query("url") url: String, @Header("x-api-key") key: String): Call<ParsedContent>

View File

@ -6,14 +6,17 @@ import com.google.gson.JsonElement
import com.google.gson.JsonParseException import com.google.gson.JsonParseException
import java.lang.reflect.Type import java.lang.reflect.Type
internal class BooleanTypeAdapter : JsonDeserializer<Boolean> { internal class BooleanTypeAdapter : JsonDeserializer<Boolean> {
@Throws(JsonParseException::class) @Throws(JsonParseException::class)
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Boolean? = override fun deserialize(
try { json: JsonElement,
json.asInt == 1 typeOfT: Type,
} catch (e: Exception) { context: JsonDeserializationContext
json.asBoolean ): Boolean? =
} try {
json.asInt == 1
} catch (e: Exception) {
json.asBoolean
}
} }

View File

@ -19,8 +19,12 @@ import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
class SelfossApi(
class SelfossApi(c: Context, callingActivity: Activity, isWithSelfSignedCert: Boolean, shouldLog: Boolean) { c: Context,
callingActivity: Activity,
isWithSelfSignedCert: Boolean,
shouldLog: Boolean
) {
private lateinit var service: SelfossService private lateinit var service: SelfossService
private val config: Config = Config(c) private val config: Config = Config(c)
@ -28,50 +32,50 @@ class SelfossApi(c: Context, callingActivity: Activity, isWithSelfSignedCert: Bo
private val password: String private val password: String
fun OkHttpClient.Builder.maybeWithSelfSigned(isWithSelfSignedCert: Boolean): OkHttpClient.Builder = fun OkHttpClient.Builder.maybeWithSelfSigned(isWithSelfSignedCert: Boolean): OkHttpClient.Builder =
if (isWithSelfSignedCert) { if (isWithSelfSignedCert) {
getUnsafeHttpClient() getUnsafeHttpClient()
} else { } else {
this this
} }
fun Credentials.createAuthenticator(): DispatchingAuthenticator = fun Credentials.createAuthenticator(): DispatchingAuthenticator =
DispatchingAuthenticator.Builder() DispatchingAuthenticator.Builder()
.with("digest", DigestAuthenticator(this)) .with("digest", DigestAuthenticator(this))
.with("basic", BasicAuthenticator(this)) .with("basic", BasicAuthenticator(this))
.build() .build()
fun DispatchingAuthenticator.getHttpClien(isWithSelfSignedCert: Boolean): OkHttpClient.Builder { fun DispatchingAuthenticator.getHttpClien(isWithSelfSignedCert: Boolean): OkHttpClient.Builder {
val authCache = ConcurrentHashMap<String, CachingAuthenticator>() val authCache = ConcurrentHashMap<String, CachingAuthenticator>()
return OkHttpClient return OkHttpClient
.Builder() .Builder()
.maybeWithSelfSigned(isWithSelfSignedCert) .maybeWithSelfSigned(isWithSelfSignedCert)
.authenticator(CachingAuthenticatorDecorator(this, authCache)) .authenticator(CachingAuthenticatorDecorator(this, authCache))
.addInterceptor(AuthenticationCacheInterceptor(authCache)) .addInterceptor(AuthenticationCacheInterceptor(authCache))
} }
init { init {
userName = config.userLogin userName = config.userLogin
password = config.userPassword password = config.userPassword
val authenticator = val authenticator =
Credentials( Credentials(
config.httpUserLogin, config.httpUserLogin,
config.httpUserPassword config.httpUserPassword
).createAuthenticator() ).createAuthenticator()
val gson = val gson =
GsonBuilder() GsonBuilder()
.registerTypeAdapter(Boolean::class.javaPrimitiveType, BooleanTypeAdapter()) .registerTypeAdapter(Boolean::class.javaPrimitiveType, BooleanTypeAdapter())
.setLenient() .setLenient()
.create() .create()
val logging = HttpLoggingInterceptor() val logging = HttpLoggingInterceptor()
logging.level = if (shouldLog) logging.level = if (shouldLog) {
HttpLoggingInterceptor.Level.BODY HttpLoggingInterceptor.Level.BODY
else } else {
HttpLoggingInterceptor.Level.NONE HttpLoggingInterceptor.Level.NONE
}
val httpClient = authenticator.getHttpClien(isWithSelfSignedCert) val httpClient = authenticator.getHttpClien(isWithSelfSignedCert)
@ -79,12 +83,12 @@ class SelfossApi(c: Context, callingActivity: Activity, isWithSelfSignedCert: Bo
try { try {
val retrofit = val retrofit =
Retrofit Retrofit
.Builder() .Builder()
.baseUrl(config.baseUrl) .baseUrl(config.baseUrl)
.client(httpClient.build()) .client(httpClient.build())
.addConverterFactory(GsonConverterFactory.create(gson)) .addConverterFactory(GsonConverterFactory.create(gson))
.build() .build()
service = retrofit.create(SelfossService::class.java) service = retrofit.create(SelfossService::class.java)
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
Config.logoutAndRedirect(c, callingActivity, config.settings.edit(), baseUrlFail = true) Config.logoutAndRedirect(c, callingActivity, config.settings.edit(), baseUrlFail = true)
@ -92,34 +96,59 @@ class SelfossApi(c: Context, callingActivity: Activity, isWithSelfSignedCert: Bo
} }
fun login(): Call<SuccessResponse> = fun login(): Call<SuccessResponse> =
service.loginToSelfoss(config.userLogin, config.userPassword) service.loginToSelfoss(config.userLogin, config.userPassword)
fun readItems(tag: String?, sourceId: Long?, search: String?, itemsNumber: Int, offset: Int): Call<List<Item>> = fun readItems(
getItems("read", tag, sourceId, search, itemsNumber, offset) tag: String?,
sourceId: Long?,
search: String?,
itemsNumber: Int,
offset: Int
): Call<List<Item>> =
getItems("read", tag, sourceId, search, itemsNumber, offset)
fun newItems(tag: String?, sourceId: Long?, search: String?, itemsNumber: Int, offset: Int): Call<List<Item>> = fun newItems(
getItems("unread", tag, sourceId, search, itemsNumber, offset) tag: String?,
sourceId: Long?,
search: String?,
itemsNumber: Int,
offset: Int
): Call<List<Item>> =
getItems("unread", tag, sourceId, search, itemsNumber, offset)
fun starredItems(tag: String?, sourceId: Long?, search: String?, itemsNumber: Int, offset: Int): Call<List<Item>> = fun starredItems(
getItems("starred", tag, sourceId, search, itemsNumber, offset) tag: String?,
sourceId: Long?,
search: String?,
itemsNumber: Int,
offset: Int
): Call<List<Item>> =
getItems("starred", tag, sourceId, search, itemsNumber, offset)
private fun getItems(type: String, tag: String?, sourceId: Long?, search: String?, items: Int, offset: Int): Call<List<Item>> = private fun getItems(
service.getItems(type, tag, sourceId, search, userName, password, items, offset) type: String,
tag: String?,
sourceId: Long?,
search: String?,
items: Int,
offset: Int
): Call<List<Item>> =
service.getItems(type, tag, sourceId, search, userName, password, items, offset)
fun markItem(itemId: String): Call<SuccessResponse> = fun markItem(itemId: String): Call<SuccessResponse> =
service.markAsRead(itemId, userName, password) service.markAsRead(itemId, userName, password)
fun unmarkItem(itemId: String): Call<SuccessResponse> = fun unmarkItem(itemId: String): Call<SuccessResponse> =
service.unmarkAsRead(itemId, userName, password) service.unmarkAsRead(itemId, userName, password)
fun readAll(ids: List<String>): Call<SuccessResponse> = fun readAll(ids: List<String>): Call<SuccessResponse> =
service.markAllAsRead(ids, userName, password) service.markAllAsRead(ids, userName, password)
fun starrItem(itemId: String): Call<SuccessResponse> = fun starrItem(itemId: String): Call<SuccessResponse> =
service.starr(itemId, userName, password) service.starr(itemId, userName, password)
fun unstarrItem(itemId: String): Call<SuccessResponse> = fun unstarrItem(itemId: String): Call<SuccessResponse> =
service.unstarr(itemId, userName, password) service.unstarr(itemId, userName, password)
val stats: Call<Stats> val stats: Call<Stats>
get() = service.stats(userName, password) get() = service.stats(userName, password)
@ -128,18 +157,23 @@ class SelfossApi(c: Context, callingActivity: Activity, isWithSelfSignedCert: Bo
get() = service.tags(userName, password) get() = service.tags(userName, password)
fun update(): Call<String> = fun update(): Call<String> =
service.update(userName, password) service.update(userName, password)
val sources: Call<List<Sources>> val sources: Call<List<Sources>>
get() = service.sources(userName, password) get() = service.sources(userName, password)
fun deleteSource(id: String): Call<SuccessResponse> = fun deleteSource(id: String): Call<SuccessResponse> =
service.deleteSource(id, userName, password) service.deleteSource(id, userName, password)
fun spouts(): Call<Map<String, Spout>> = fun spouts(): Call<Map<String, Spout>> =
service.spouts(userName, password) service.spouts(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)
fun createSource(
title: String,
url: String,
spout: String,
tags: String,
filter: String
): Call<SuccessResponse> =
service.createSource(title, url, spout, tags, filter, userName, password)
} }

View File

@ -9,59 +9,69 @@ import apps.amine.bou.readerforselfoss.utils.Config
import apps.amine.bou.readerforselfoss.utils.isEmptyOrNullOrNullString import apps.amine.bou.readerforselfoss.utils.isEmptyOrNullOrNullString
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
private fun constructUrl(config: Config?, path: String, file: String): String { private fun constructUrl(config: Config?, path: String, file: String): String {
val baseUriBuilder = Uri.parse(config!!.baseUrl).buildUpon() val baseUriBuilder = Uri.parse(config!!.baseUrl).buildUpon()
baseUriBuilder.appendPath(path).appendPath(file) baseUriBuilder.appendPath(path).appendPath(file)
return if (file.isEmptyOrNullOrNullString()) "" return if (file.isEmptyOrNullOrNullString()) {
else baseUriBuilder.toString() ""
} else {
baseUriBuilder.toString()
}
} }
data class Tag(
data class Tag(@SerializedName("tag") val tag: String, @SerializedName("tag") val tag: String,
@SerializedName("color") val color: String, @SerializedName("color") val color: String,
@SerializedName("unread") val unread: Int) @SerializedName("unread") val unread: Int
)
class SuccessResponse(@SerializedName("success") val success: Boolean) { class SuccessResponse(@SerializedName("success") val success: Boolean) {
val isSuccess: Boolean val isSuccess: Boolean
get() = success get() = success
} }
class Stats(@SerializedName("total") val total: Int, class Stats(
@SerializedName("unread") val unread: Int, @SerializedName("total") val total: Int,
@SerializedName("starred") val starred: Int) @SerializedName("unread") val unread: Int,
@SerializedName("starred") val starred: Int
)
data class Spout(@SerializedName("name") val name: String, data class Spout(
@SerializedName("description") val description: String) @SerializedName("name") val name: String,
@SerializedName("description") val description: String
)
data class Sources(@SerializedName("id") val id: String, data class Sources(
@SerializedName("title") val title: String, @SerializedName("id") val id: String,
@SerializedName("tags") val tags: String, @SerializedName("title") val title: String,
@SerializedName("spout") val spout: String, @SerializedName("tags") val tags: String,
@SerializedName("error") val error: String, @SerializedName("spout") val spout: String,
@SerializedName("icon") val icon: String) { @SerializedName("error") val error: String,
@SerializedName("icon") val icon: String
) {
var config: Config? = null var config: Config? = null
fun getIcon(app: Context): String { fun getIcon(app: Context): String {
if (config == null) { if (config == null) {
config = Config(app) config = Config(app)
} }
return constructUrl(config,"favicons", icon) return constructUrl(config, "favicons", icon)
} }
} }
data class Item(@SerializedName("id") val id: String, data class Item(
@SerializedName("datetime") val datetime: String, @SerializedName("id") val id: String,
@SerializedName("title") val title: String, @SerializedName("datetime") val datetime: String,
@SerializedName("content") val content: String, @SerializedName("title") val title: String,
@SerializedName("unread") val unread: Boolean, @SerializedName("content") val content: String,
@SerializedName("starred") val starred: Boolean, @SerializedName("unread") val unread: Boolean,
@SerializedName("thumbnail") val thumbnail: String, @SerializedName("starred") val starred: Boolean,
@SerializedName("icon") val icon: String, @SerializedName("thumbnail") val thumbnail: String,
@SerializedName("link") val link: String, @SerializedName("icon") val icon: String,
@SerializedName("sourcetitle") val sourcetitle: String) : Parcelable { @SerializedName("link") val link: String,
@SerializedName("sourcetitle") val sourcetitle: String
) : Parcelable {
var config: Config? = null var config: Config? = null
@ -139,5 +149,4 @@ data class Item(@SerializedName("id") val id: String,
return stringUrl return stringUrl
} }
} }

View File

@ -10,97 +10,109 @@ import retrofit2.http.POST
import retrofit2.http.Path import retrofit2.http.Path
import retrofit2.http.Query import retrofit2.http.Query
internal interface SelfossService { internal interface SelfossService {
@GET("login") @GET("login")
fun loginToSelfoss(@Query("username") username: String, @Query("password") password: String): Call<SuccessResponse> fun loginToSelfoss(@Query("username") username: String, @Query("password") password: String): Call<SuccessResponse>
@GET("items") @GET("items")
fun getItems(@Query("type") type: String, fun getItems(
@Query("tag") tag: String?, @Query("type") type: String,
@Query("source") source: Long?, @Query("tag") tag: String?,
@Query("search") search: String?, @Query("source") source: Long?,
@Query("username") username: String, @Query("search") search: String?,
@Query("password") password: String, @Query("username") username: String,
@Query("items") items: Int, @Query("password") password: String,
@Query("offset") offset: Int): Call<List<Item>> @Query("items") items: Int,
@Query("offset") offset: Int
): Call<List<Item>>
@Headers("Content-Type: application/x-www-form-urlencoded") @Headers("Content-Type: application/x-www-form-urlencoded")
@POST("mark/{id}") @POST("mark/{id}")
fun markAsRead(@Path("id") id: String, fun markAsRead(
@Query("username") username: String, @Path("id") id: String,
@Query("password") password: String): Call<SuccessResponse> @Query("username") username: String,
@Query("password") password: String
): Call<SuccessResponse>
@Headers("Content-Type: application/x-www-form-urlencoded") @Headers("Content-Type: application/x-www-form-urlencoded")
@POST("unmark/{id}") @POST("unmark/{id}")
fun unmarkAsRead(@Path("id") id: String, fun unmarkAsRead(
@Query("username") username: String, @Path("id") id: String,
@Query("password") password: String): Call<SuccessResponse> @Query("username") username: String,
@Query("password") password: String
): Call<SuccessResponse>
@FormUrlEncoded @FormUrlEncoded
@POST("mark") @POST("mark")
fun markAllAsRead(@Field("ids[]") ids: List<String>, fun markAllAsRead(
@Query("username") username: String, @Field("ids[]") ids: List<String>,
@Query("password") password: String): Call<SuccessResponse> @Query("username") username: String,
@Query("password") password: String
): Call<SuccessResponse>
@Headers("Content-Type: application/x-www-form-urlencoded") @Headers("Content-Type: application/x-www-form-urlencoded")
@POST("starr/{id}") @POST("starr/{id}")
fun starr(@Path("id") id: String, fun starr(
@Query("username") username: String, @Path("id") id: String,
@Query("password") password: String): Call<SuccessResponse> @Query("username") username: String,
@Query("password") password: String
): Call<SuccessResponse>
@Headers("Content-Type: application/x-www-form-urlencoded") @Headers("Content-Type: application/x-www-form-urlencoded")
@POST("unstarr/{id}") @POST("unstarr/{id}")
fun unstarr(@Path("id") id: String, fun unstarr(
@Query("username") username: String, @Path("id") id: String,
@Query("password") password: String): Call<SuccessResponse> @Query("username") username: String,
@Query("password") password: String
): Call<SuccessResponse>
@GET("stats") @GET("stats")
fun stats(@Query("username") username: String, fun stats(
@Query("password") password: String): Call<Stats> @Query("username") username: String,
@Query("password") password: String
): Call<Stats>
@GET("tags") @GET("tags")
fun tags(@Query("username") username: String, fun tags(
@Query("password") password: String): Call<List<Tag>> @Query("username") username: String,
@Query("password") password: String
): Call<List<Tag>>
@GET("update") @GET("update")
fun update(@Query("username") username: String, fun update(
@Query("password") password: String): Call<String> @Query("username") username: String,
@Query("password") password: String
): Call<String>
@GET("sources/spouts") @GET("sources/spouts")
fun spouts(@Query("username") username: String, fun spouts(
@Query("password") password: String): Call<Map<String, Spout>> @Query("username") username: String,
@Query("password") password: String
): Call<Map<String, Spout>>
@GET("sources/list") @GET("sources/list")
fun sources(@Query("username") username: String, fun sources(
@Query("password") password: String): Call<List<Sources>> @Query("username") username: String,
@Query("password") password: String
): Call<List<Sources>>
@DELETE("source/{id}") @DELETE("source/{id}")
fun deleteSource(@Path("id") id: String, fun deleteSource(
@Query("username") username: String, @Path("id") id: String,
@Query("password") password: String): Call<SuccessResponse> @Query("username") username: String,
@Query("password") password: String
): Call<SuccessResponse>
@FormUrlEncoded @FormUrlEncoded
@POST("source") @POST("source")
fun createSource(@Field("title") title: String, fun createSource(
@Field("url") url: String, @Field("title") title: String,
@Field("spout") spout: String, @Field("url") url: String,
@Field("tags") tags: String, @Field("spout") spout: String,
@Field("filter") filter: String, @Field("tags") tags: String,
@Query("username") username: String, @Field("filter") filter: String,
@Query("password") password: String): Call<SuccessResponse> @Query("username") username: String,
@Query("password") password: String
): Call<SuccessResponse>
} }

View File

@ -6,7 +6,6 @@ import android.support.annotation.ColorInt
import android.util.TypedValue import android.util.TypedValue
import apps.amine.bou.readerforselfoss.R import apps.amine.bou.readerforselfoss.R
class AppColors(a: Activity) { class AppColors(a: Activity) {
@ColorInt val accent: Int @ColorInt val accent: Int
@ColorInt val dark: Int @ColorInt val dark: Int
@ -20,7 +19,7 @@ class AppColors(a: Activity) {
val method = wrapper!!.getMethod("getThemeResId") val method = wrapper!!.getMethod("getThemeResId")
method.isAccessible = true method.isAccessible = true
isDarkTheme = when(method.invoke(a.baseContext)) { isDarkTheme = when (method.invoke(a.baseContext)) {
R.style.NoBarTealOrangeDark, R.style.NoBarTealOrangeDark,
R.style.NoBarDark, R.style.NoBarDark,
R.style.NoBarBlueAmberDark, R.style.NoBarBlueAmberDark,

View File

@ -4,4 +4,4 @@ import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
import retrofit2.Response import retrofit2.Response
fun Response<SuccessResponse>.succeeded(): Boolean = fun Response<SuccessResponse>.succeeded(): Boolean =
this.code() === 200 && this.body() != null && this.body()!!.isSuccess this.code() === 200 && this.body() != null && this.body()!!.isSuccess

View File

@ -8,13 +8,14 @@ import android.support.v7.app.AlertDialog
import apps.amine.bou.readerforselfoss.R import apps.amine.bou.readerforselfoss.R
import com.google.firebase.remoteconfig.FirebaseRemoteConfig import com.google.firebase.remoteconfig.FirebaseRemoteConfig
fun String?.isEmptyOrNullOrNullString(): Boolean = fun String?.isEmptyOrNullOrNullString(): Boolean =
this == null || this == "null" || this.isEmpty() this == null || this == "null" || this.isEmpty()
fun Context.checkApkVersion(settings: SharedPreferences, fun Context.checkApkVersion(
editor: SharedPreferences.Editor, settings: SharedPreferences,
mFirebaseRemoteConfig: FirebaseRemoteConfig) = { editor: SharedPreferences.Editor,
mFirebaseRemoteConfig: FirebaseRemoteConfig
) = {
fun isThereAnUpdate() { fun isThereAnUpdate() {
val APK_LINK = "github_apk" val APK_LINK = "github_apk"
@ -24,31 +25,35 @@ fun Context.checkApkVersion(settings: SharedPreferences,
val alertDialog = AlertDialog.Builder(this).create() val alertDialog = AlertDialog.Builder(this).create()
alertDialog.setTitle(getString(R.string.new_apk_available_title)) alertDialog.setTitle(getString(R.string.new_apk_available_title))
alertDialog.setMessage(getString(R.string.new_apk_available_message)) alertDialog.setMessage(getString(R.string.new_apk_available_message))
alertDialog.setButton(AlertDialog.BUTTON_POSITIVE, getString(R.string.new_apk_available_get)) { _, _ -> alertDialog.setButton(
AlertDialog.BUTTON_POSITIVE,
getString(R.string.new_apk_available_get)
) { _, _ ->
editor.putString(APK_LINK, apkLink) editor.putString(APK_LINK, apkLink)
editor.apply() editor.apply()
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(apkLink)) val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(apkLink))
startActivity(browserIntent) startActivity(browserIntent)
} }
alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, getString(R.string.new_apk_available_no), alertDialog.setButton(
{ dialog, _ -> AlertDialog.BUTTON_NEUTRAL, getString(R.string.new_apk_available_no),
editor.putString(APK_LINK, apkLink) { dialog, _ ->
editor.apply() editor.putString(APK_LINK, apkLink)
dialog.dismiss() editor.apply()
}) dialog.dismiss()
}
)
alertDialog.show() alertDialog.show()
} }
} }
mFirebaseRemoteConfig.fetch(43200) mFirebaseRemoteConfig.fetch(43200)
.addOnCompleteListener { task -> .addOnCompleteListener { task ->
if (task.isSuccessful) { if (task.isSuccessful) {
mFirebaseRemoteConfig.activateFetched() mFirebaseRemoteConfig.activateFetched()
} }
isThereAnUpdate() isThereAnUpdate()
} }
} }
fun String.longHash(): Long { fun String.longHash(): Long {
@ -62,11 +67,12 @@ fun String.longHash(): Long {
return h return h
} }
fun String.toStringUriWithHttp() = fun String.toStringUriWithHttp(): String =
if (!this.startsWith("https://") && !this.startsWith("http://")) if (!this.startsWith("https://") && !this.startsWith("http://")) {
"http://" + this "http://" + this
else } else {
this this
}
fun Context.shareLink(itemUrl: String) { fun Context.shareLink(itemUrl: String) {
val sendIntent = Intent() val sendIntent = Intent()
@ -74,5 +80,10 @@ fun Context.shareLink(itemUrl: String) {
sendIntent.action = Intent.ACTION_SEND sendIntent.action = Intent.ACTION_SEND
sendIntent.putExtra(Intent.EXTRA_TEXT, itemUrl.toStringUriWithHttp()) sendIntent.putExtra(Intent.EXTRA_TEXT, itemUrl.toStringUriWithHttp())
sendIntent.type = "text/plain" sendIntent.type = "text/plain"
startActivity(Intent.createChooser(sendIntent, getString(R.string.share)).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)) startActivity(
Intent.createChooser(
sendIntent,
getString(R.string.share)
).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
)
} }

View File

@ -6,7 +6,6 @@ import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import apps.amine.bou.readerforselfoss.LoginActivity import apps.amine.bou.readerforselfoss.LoginActivity
class Config(c: Context) { class Config(c: Context) {
val settings: SharedPreferences = c.getSharedPreferences(settingsName, Context.MODE_PRIVATE) val settings: SharedPreferences = c.getSharedPreferences(settingsName, Context.MODE_PRIVATE)
@ -26,21 +25,23 @@ class Config(c: Context) {
val httpUserPassword: String val httpUserPassword: String
get() = settings.getString("httpPassword", "") get() = settings.getString("httpPassword", "")
companion object { companion object {
val settingsName = "paramsselfoss" val settingsName = "paramsselfoss"
fun logoutAndRedirect(c: Context, fun logoutAndRedirect(
callingActivity: Activity, c: Context,
editor: SharedPreferences.Editor, callingActivity: Activity,
baseUrlFail: Boolean = false): Boolean { editor: SharedPreferences.Editor,
baseUrlFail: Boolean = false
): Boolean {
editor.remove("url") editor.remove("url")
editor.remove("login") editor.remove("login")
editor.remove("password") editor.remove("password")
editor.apply() editor.apply()
val intent = Intent(c, LoginActivity::class.java) val intent = Intent(c, LoginActivity::class.java)
if (baseUrlFail) if (baseUrlFail) {
intent.putExtra("baseUrlFail", baseUrlFail) intent.putExtra("baseUrlFail", baseUrlFail)
}
c.startActivity(intent) c.startActivity(intent)
callingActivity.finish() callingActivity.finish()
return true return true

View File

@ -8,32 +8,36 @@ import javax.net.ssl.TrustManager
import javax.net.ssl.X509TrustManager import javax.net.ssl.X509TrustManager
fun getUnsafeHttpClient() = fun getUnsafeHttpClient() =
try { try {
// Create a trust manager that does not validate certificate chains // Create a trust manager that does not validate certificate chains
val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager { val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager {
override fun getAcceptedIssuers(): Array<X509Certificate> = override fun getAcceptedIssuers(): Array<X509Certificate> =
arrayOf() arrayOf()
@Throws(CertificateException::class) @Throws(CertificateException::class)
override fun checkClientTrusted(chain: Array<java.security.cert.X509Certificate>, authType: String) { override fun checkClientTrusted(
} chain: Array<java.security.cert.X509Certificate>,
authType: String
) {
}
@Throws(CertificateException::class) @Throws(CertificateException::class)
override fun checkServerTrusted(chain: Array<java.security.cert.X509Certificate>, authType: String) { override fun checkServerTrusted(
} chain: Array<java.security.cert.X509Certificate>,
authType: String
) {
}
})
}) // Install the all-trusting trust manager
val sslContext = SSLContext.getInstance("SSL")
sslContext.init(null, trustAllCerts, java.security.SecureRandom())
// Install the all-trusting trust manager val sslSocketFactory = sslContext.socketFactory
val sslContext = SSLContext.getInstance("SSL")
sslContext.init(null, trustAllCerts, java.security.SecureRandom())
val sslSocketFactory = sslContext.socketFactory OkHttpClient.Builder()
.sslSocketFactory(sslSocketFactory, trustAllCerts[0] as X509TrustManager)
OkHttpClient.Builder() .hostnameVerifier { _, _ -> true }
.sslSocketFactory(sslSocketFactory, trustAllCerts[0] as X509TrustManager) } catch (e: Exception) {
.hostnameVerifier { _, _ -> true } throw RuntimeException(e)
}
} catch (e: Exception) {
throw RuntimeException(e)
}

View File

@ -6,7 +6,6 @@ import java.text.ParseException
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
fun String.toTextDrawableString(): String { fun String.toTextDrawableString(): String {
val textDrawable = StringBuilder() val textDrawable = StringBuilder()
for (s in this.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) { for (s in this.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) {
@ -18,10 +17,10 @@ fun String.toTextDrawableString(): String {
fun Item.sourceAndDateText(): String { fun Item.sourceAndDateText(): String {
val formattedDate: String = try { val formattedDate: String = try {
" " + DateUtils.getRelativeTimeSpanString( " " + DateUtils.getRelativeTimeSpanString(
SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(this.datetime).time, SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(this.datetime).time,
Date().time, Date().time,
DateUtils.MINUTE_IN_MILLIS, DateUtils.MINUTE_IN_MILLIS,
DateUtils.FORMAT_ABBREV_RELATIVE DateUtils.FORMAT_ABBREV_RELATIVE
) )
} catch (e: ParseException) { } catch (e: ParseException) {
e.printStackTrace() e.printStackTrace()

View File

@ -14,15 +14,17 @@ import apps.amine.bou.readerforselfoss.ReaderActivity
import apps.amine.bou.readerforselfoss.api.selfoss.Item import apps.amine.bou.readerforselfoss.api.selfoss.Item
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
import okhttp3.HttpUrl import okhttp3.HttpUrl
import xyz.klinker.android.drag_dismiss.DragDismissIntentBuilder
fun Context.buildCustomTabsIntent(): CustomTabsIntent { fun Context.buildCustomTabsIntent(): CustomTabsIntent {
val actionIntent = Intent(Intent.ACTION_SEND) val actionIntent = Intent(Intent.ACTION_SEND)
actionIntent.type = "text/plain" actionIntent.type = "text/plain"
val createPendingShareIntent: PendingIntent = PendingIntent.getActivity(this, 0, actionIntent, 0) val createPendingShareIntent: PendingIntent = PendingIntent.getActivity(
this,
0,
actionIntent,
0
)
val intentBuilder = CustomTabsIntent.Builder() val intentBuilder = CustomTabsIntent.Builder()
@ -32,32 +34,40 @@ fun Context.buildCustomTabsIntent(): CustomTabsIntent {
intentBuilder.setShowTitle(true) intentBuilder.setShowTitle(true)
intentBuilder.setStartAnimations(this, intentBuilder.setStartAnimations(
this,
R.anim.slide_in_right, R.anim.slide_in_right,
R.anim.slide_out_left) R.anim.slide_out_left
intentBuilder.setExitAnimations(this, )
intentBuilder.setExitAnimations(
this,
android.R.anim.slide_in_left, android.R.anim.slide_in_left,
android.R.anim.slide_out_right) android.R.anim.slide_out_right
)
val closeicon = BitmapFactory.decodeResource(resources, R.drawable.ic_close_white_24dp) val closeicon = BitmapFactory.decodeResource(resources, R.drawable.ic_close_white_24dp)
intentBuilder.setCloseButtonIcon(closeicon) intentBuilder.setCloseButtonIcon(closeicon)
val shareLabel = this.getString(R.string.label_share) val shareLabel = this.getString(R.string.label_share)
val icon = BitmapFactory.decodeResource(resources, val icon = BitmapFactory.decodeResource(
R.drawable.ic_share_white_24dp) resources,
R.drawable.ic_share_white_24dp
)
intentBuilder.setActionButton(icon, shareLabel, createPendingShareIntent) intentBuilder.setActionButton(icon, shareLabel, createPendingShareIntent)
return intentBuilder.build() return intentBuilder.build()
} }
fun Context.openItemUrlInternally(linkDecoded: String, fun Context.openItemUrlInternally(
content: String, linkDecoded: String,
image: String, content: String,
title: String, image: String,
source: String, title: String,
customTabsIntent: CustomTabsIntent, source: String,
articleViewer: Boolean, customTabsIntent: CustomTabsIntent,
app: Activity) { articleViewer: Boolean,
app: Activity
) {
if (articleViewer) { if (articleViewer) {
val intent = Intent(this, ReaderActivity::class.java) val intent = Intent(this, ReaderActivity::class.java)
@ -76,7 +86,10 @@ fun Context.openItemUrlInternally(linkDecoded: String,
app.startActivity(intent) app.startActivity(intent)
} else { } else {
try { try {
CustomTabActivityHelper.openCustomTab(app, customTabsIntent, Uri.parse(linkDecoded) CustomTabActivityHelper.openCustomTab(
app,
customTabsIntent,
Uri.parse(linkDecoded)
) { _, uri -> ) { _, uri ->
val intent = Intent(Intent.ACTION_VIEW, uri) val intent = Intent(Intent.ACTION_VIEW, uri)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
@ -88,23 +101,38 @@ fun Context.openItemUrlInternally(linkDecoded: String,
} }
} }
fun Context.openItemUrl(linkDecoded: String, fun Context.openItemUrl(
content: String, linkDecoded: String,
image: String, content: String,
title: String, image: String,
source: String, title: String,
customTabsIntent: CustomTabsIntent, source: String,
internalBrowser: Boolean, customTabsIntent: CustomTabsIntent,
articleViewer: Boolean, internalBrowser: Boolean,
app: Activity) { articleViewer: Boolean,
app: Activity
) {
if (!linkDecoded.isUrlValid()) { if (!linkDecoded.isUrlValid()) {
Toast.makeText(this, this.getString(R.string.cant_open_invalid_url), Toast.LENGTH_LONG).show() Toast.makeText(
this,
this.getString(R.string.cant_open_invalid_url),
Toast.LENGTH_LONG
).show()
} else { } else {
if (!internalBrowser) { if (!internalBrowser) {
openInBrowser(linkDecoded, app) openInBrowser(linkDecoded, app)
} else { } else {
this.openItemUrlInternally(linkDecoded, content, image, title, source, customTabsIntent, articleViewer, app) this.openItemUrlInternally(
linkDecoded,
content,
image,
title,
source,
customTabsIntent,
articleViewer,
app
)
} }
} }
} }

View File

@ -6,18 +6,39 @@ import android.support.design.widget.FloatingActionButton
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View import android.view.View
class ScrollAwareFABBehavior(context: Context, attrs: AttributeSet) : CoordinatorLayout.Behavior<FloatingActionButton>() { class ScrollAwareFABBehavior(
context: Context,
attrs: AttributeSet
) : CoordinatorLayout.Behavior<FloatingActionButton>() {
override fun onStartNestedScroll(coordinatorLayout: CoordinatorLayout, child: FloatingActionButton, override fun onStartNestedScroll(
directTargetChild: View, target: View, nestedScrollAxes: Int): Boolean { coordinatorLayout: CoordinatorLayout,
child: FloatingActionButton,
directTargetChild: View,
target: View,
nestedScrollAxes: Int
): Boolean {
return true return true
} }
override fun onNestedScroll(coordinatorLayout: CoordinatorLayout, override fun onNestedScroll(
child: FloatingActionButton, coordinatorLayout: CoordinatorLayout,
target: View, dxConsumed: Int, dyConsumed: Int, child: FloatingActionButton,
dxUnconsumed: Int, dyUnconsumed: Int) { target: View,
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed) dxConsumed: Int,
dyConsumed: Int,
dxUnconsumed: Int,
dyUnconsumed: Int
) {
super.onNestedScroll(
coordinatorLayout,
child,
target,
dxConsumed,
dyConsumed,
dxUnconsumed,
dyUnconsumed
)
if (dyConsumed > 0 && child.visibility == View.VISIBLE) { if (dyConsumed > 0 && child.visibility == View.VISIBLE) {
child.hide(object : FloatingActionButton.OnVisibilityChangedListener() { child.hide(object : FloatingActionButton.OnVisibilityChangedListener() {
override fun onHidden(fab: FloatingActionButton?) { override fun onHidden(fab: FloatingActionButton?) {
@ -25,7 +46,6 @@ class ScrollAwareFABBehavior(context: Context, attrs: AttributeSet) : Coordinato
fab!!.visibility = View.INVISIBLE fab!!.visibility = View.INVISIBLE
} }
}) })
} else if (dyConsumed < 0 && child.visibility != View.VISIBLE) { } else if (dyConsumed < 0 && child.visibility != View.VISIBLE) {
child.show() child.show()
} }

View File

@ -2,7 +2,6 @@ package apps.amine.bou.readerforselfoss.utils.bottombar
import com.ashokvarma.bottomnavigation.TextBadgeItem import com.ashokvarma.bottomnavigation.TextBadgeItem
fun TextBadgeItem.removeBadge(): TextBadgeItem { fun TextBadgeItem.removeBadge(): TextBadgeItem {
this.setText("") this.setText("")
this.hide() this.hide()
@ -10,7 +9,4 @@ fun TextBadgeItem.removeBadge(): TextBadgeItem {
} }
fun TextBadgeItem.maybeShow(): TextBadgeItem = fun TextBadgeItem.maybeShow(): TextBadgeItem =
if (this.isHidden) if (this.isHidden) this.show() else this
this.show()
else
this

View File

@ -8,8 +8,6 @@ import android.widget.TextView
import apps.amine.bou.readerforselfoss.R import apps.amine.bou.readerforselfoss.R
open class CustomBaseViewHolder(var view: View) : RecyclerView.ViewHolder(view) { open class CustomBaseViewHolder(var view: View) : RecyclerView.ViewHolder(view) {
var icon: ImageView = view.findViewById(R.id.material_drawer_icon) var icon: ImageView = view.findViewById(R.id.material_drawer_icon)
var name: TextView = view.findViewById(R.id.material_drawer_name) var name: TextView = view.findViewById(R.id.material_drawer_name)

View File

@ -15,8 +15,6 @@ import com.mikepenz.materialdrawer.util.DrawerImageLoader
import com.mikepenz.materialdrawer.util.DrawerUIUtils import com.mikepenz.materialdrawer.util.DrawerUIUtils
import com.mikepenz.materialize.util.UIUtils import com.mikepenz.materialize.util.UIUtils
abstract class CustomUrlBasePrimaryDrawerItem<T, VH : RecyclerView.ViewHolder> : BaseDrawerItem<T, VH>() { abstract class CustomUrlBasePrimaryDrawerItem<T, VH : RecyclerView.ViewHolder> : BaseDrawerItem<T, VH>() {
fun withIcon(url: String): T { fun withIcon(url: String): T {
this.icon = ImageHolder(url) this.icon = ImageHolder(url)
@ -77,7 +75,10 @@ abstract class CustomUrlBasePrimaryDrawerItem<T, VH : RecyclerView.ViewHolder> :
val selectedIconColor = getSelectedIconColor(ctx) val selectedIconColor = getSelectedIconColor(ctx)
//set the background for the item //set the background for the item
UIUtils.setBackground(viewHolder.view, UIUtils.getSelectableBackground(ctx, selectedColor, true)) UIUtils.setBackground(
viewHolder.view,
UIUtils.getSelectableBackground(ctx, selectedColor, true)
)
//set the text for the name //set the text for the name
StringHolder.applyTo(this.getName(), viewHolder.name) StringHolder.applyTo(this.getName(), viewHolder.name)
//set the text for the description or hide //set the text for the description or hide
@ -86,8 +87,11 @@ abstract class CustomUrlBasePrimaryDrawerItem<T, VH : RecyclerView.ViewHolder> :
//set the colors for textViews //set the colors for textViews
viewHolder.name.setTextColor(getTextColorStateList(color, selectedTextColor)) viewHolder.name.setTextColor(getTextColorStateList(color, selectedTextColor))
//set the description text color //set the description text color
ColorHolder.applyToOr(descriptionTextColor, ColorHolder.applyToOr(
viewHolder.description, getTextColorStateList(color, selectedTextColor)) descriptionTextColor,
viewHolder.description,
getTextColorStateList(color, selectedTextColor)
)
//define the typeface for our textViews //define the typeface for our textViews
if (getTypeface() != null) { if (getTypeface() != null) {

View File

@ -10,7 +10,6 @@ import com.mikepenz.materialdrawer.holder.BadgeStyle
import com.mikepenz.materialdrawer.holder.StringHolder import com.mikepenz.materialdrawer.holder.StringHolder
import com.mikepenz.materialdrawer.model.interfaces.ColorfulBadgeable import com.mikepenz.materialdrawer.model.interfaces.ColorfulBadgeable
class CustomUrlPrimaryDrawerItem : CustomUrlBasePrimaryDrawerItem<CustomUrlPrimaryDrawerItem, CustomUrlPrimaryDrawerItem.ViewHolder>(), ColorfulBadgeable<CustomUrlPrimaryDrawerItem> { class CustomUrlPrimaryDrawerItem : CustomUrlBasePrimaryDrawerItem<CustomUrlPrimaryDrawerItem, CustomUrlPrimaryDrawerItem.ViewHolder>(), ColorfulBadgeable<CustomUrlPrimaryDrawerItem> {
protected var mBadge: StringHolder = StringHolder("") protected var mBadge: StringHolder = StringHolder("")
protected var mBadgeStyle = BadgeStyle() protected var mBadgeStyle = BadgeStyle()
@ -64,7 +63,10 @@ class CustomUrlPrimaryDrawerItem : CustomUrlBasePrimaryDrawerItem<CustomUrlPrima
val badgeVisible = StringHolder.applyToOrHide(mBadge, viewHolder.badge) val badgeVisible = StringHolder.applyToOrHide(mBadge, viewHolder.badge)
//style the badge if it is visible //style the badge if it is visible
if (badgeVisible) { if (badgeVisible) {
mBadgeStyle.style(viewHolder.badge, getTextColorStateList(getColor(ctx), getSelectedTextColor(ctx))) mBadgeStyle.style(
viewHolder.badge,
getTextColorStateList(getColor(ctx), getSelectedTextColor(ctx))
)
viewHolder.badgeContainer.visibility = View.VISIBLE viewHolder.badgeContainer.visibility = View.VISIBLE
} else { } else {
viewHolder.badgeContainer.visibility = View.GONE viewHolder.badgeContainer.visibility = View.GONE
@ -86,6 +88,5 @@ class CustomUrlPrimaryDrawerItem : CustomUrlBasePrimaryDrawerItem<CustomUrlPrima
class ViewHolder(view: View) : CustomBaseViewHolder(view) { class ViewHolder(view: View) : CustomBaseViewHolder(view) {
val badgeContainer: View = view.findViewById(R.id.material_drawer_badge_container) val badgeContainer: View = view.findViewById(R.id.material_drawer_badge_container)
val badge: TextView = view.findViewById(R.id.material_drawer_badge) val badge: TextView = view.findViewById(R.id.material_drawer_badge)
} }
} }

View File

@ -8,22 +8,32 @@ import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.BitmapImageViewTarget import com.bumptech.glide.request.target.BitmapImageViewTarget
fun Context.bitmapCenterCrop(url: String, iv: ImageView) = fun Context.bitmapCenterCrop(url: String, iv: ImageView) =
Glide.with(this).asBitmap().load(url).apply(RequestOptions.centerCropTransform()).into(iv) Glide.with(this)
.asBitmap()
.load(url)
.apply(RequestOptions.centerCropTransform())
.into(iv)
fun Context.bitmapFitCenter(url: String, iv: ImageView) = fun Context.bitmapFitCenter(url: String, iv: ImageView) =
Glide.with(this).asBitmap().load(url).apply(RequestOptions.fitCenterTransform()).into(iv) Glide.with(this)
.asBitmap()
.load(url)
.apply(RequestOptions.fitCenterTransform())
.into(iv)
fun Context.circularBitmapDrawable(url: String, iv: ImageView) = fun Context.circularBitmapDrawable(url: String, iv: ImageView) =
Glide.with(this) Glide.with(this)
.asBitmap() .asBitmap()
.load(url) .load(url)
.apply(RequestOptions.centerCropTransform()). .apply(RequestOptions.centerCropTransform())
into(object : BitmapImageViewTarget(iv) { .into(object : BitmapImageViewTarget(iv) {
override fun setResource(resource: Bitmap?) { override fun setResource(resource: Bitmap?) {
val circularBitmapDrawable = RoundedBitmapDrawableFactory.create(resources, resource) val circularBitmapDrawable = RoundedBitmapDrawableFactory.create(
circularBitmapDrawable.isCircular = true resources,
iv.setImageDrawable(circularBitmapDrawable) resource
} )
}) circularBitmapDrawable.isCircular = true
iv.setImageDrawable(circularBitmapDrawable)
}
})

View File

@ -10,10 +10,10 @@ import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.module.GlideModule import com.bumptech.glide.module.GlideModule
import java.io.InputStream import java.io.InputStream
class SelfSignedGlideModule : GlideModule { class SelfSignedGlideModule : GlideModule {
override fun applyOptions(context: Context?, builder: GlideBuilder?) {} override fun applyOptions(context: Context?, builder: GlideBuilder?) {
}
override fun registerComponents(context: Context?, glide: Glide?, registry: Registry?) { override fun registerComponents(context: Context?, glide: Glide?, registry: Registry?) {
@ -22,11 +22,12 @@ class SelfSignedGlideModule : GlideModule {
if (pref.getBoolean("isSelfSignedCert", false)) { if (pref.getBoolean("isSelfSignedCert", false)) {
val client = getUnsafeHttpClient().build() val client = getUnsafeHttpClient().build()
registry?.append(GlideUrl::class.java, InputStream::class.java, registry?.append(
com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader.Factory(client)) GlideUrl::class.java,
InputStream::class.java,
com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader.Factory(client)
)
} }
} }
} }
} }

View File

@ -35,7 +35,6 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="end|bottom|right" android:layout_gravity="end|bottom|right"
android:src="@drawable/ic_add" android:src="@drawable/ic_add"
android:tint="?android:textColorPrimary"
android:paddingBottom="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingTop="@dimen/activity_vertical_margin" android:paddingTop="@dimen/activity_vertical_margin"
android:layout_alignParentBottom="true" android:layout_alignParentBottom="true"

View File

@ -1,55 +1,66 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:minHeight="88dp"> android:minHeight="88dp">
<android.support.constraint.Guideline
android:id="@+id/guideline4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_begin="99dp" />
<ImageView <ImageView
android:id="@+id/itemImage" android:id="@+id/itemImage"
android:layout_width="88dp" android:layout_width="88dp"
android:layout_height="88dp" android:layout_height="88dp"
android:src="@color/about_libraries_accent" app:layout_constraintBottom_toTopOf="@+id/guideline4"
app:layout_constraintBottom_toTopOf="@+id/actionBar" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toTopOf="parent"
/>
<TextView <TextView
android:id="@+id/title" android:id="@+id/title"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:fontFamily="sans-serif" android:fontFamily="sans-serif"
android:gravity="start" android:gravity="start"
android:maxLines="3"
android:ellipsize="end"
android:textAlignment="viewStart" android:textAlignment="viewStart"
android:textAllCaps="false" android:textAllCaps="false"
android:textSize="16sp" android:textSize="16sp"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/sourceTitleAndDate"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0" app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toRightOf="@+id/itemImage" app:layout_constraintStart_toEndOf="@+id/itemImage"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
tools:text="Titre" /> tools:text="Titre" />
<TextView <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/sourceTitleAndDate" android:id="@+id/sourceTitleAndDate"
android:textSize="14sp" android:layout_width="0dp"
tools:text="Google Actualité Il y a 5h" android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
android:gravity="start"
android:textAlignment="viewStart" android:textAlignment="viewStart"
app:layout_constraintTop_toBottomOf="@+id/title" android:textSize="14sp"
android:layout_marginTop="8dp" app:layout_constraintBottom_toTopOf="@+id/guideline4"
app:layout_constraintLeft_toLeftOf="@+id/title" app:layout_constraintEnd_toEndOf="parent"
android:gravity="start" /> app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@+id/itemImage"
tools:text="Google Actualité Il y a 5h" />
<RelativeLayout <RelativeLayout
android:id="@+id/actionBar" android:id="@+id/actionBar"
@ -58,9 +69,11 @@
android:background="#BBBBBB" android:background="#BBBBBB"
android:visibility="gone" android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/sourceTitleAndDate"> app:layout_constraintTop_toTopOf="@+id/guideline4"
app:layout_constraintVertical_bias="1.0"
tools:visibility="visible">
<com.like.LikeButton <com.like.LikeButton
android:id="@+id/favButton" android:id="@+id/favButton"
@ -68,7 +81,6 @@
android:layout_height="35dp" android:layout_height="35dp"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:layout_alignParentRight="true" android:layout_alignParentRight="true"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
android:layout_marginRight="8dp" android:layout_marginRight="8dp"
@ -110,7 +122,7 @@
android:padding="4dp" android:padding="4dp"
android:scaleType="centerCrop" android:scaleType="centerCrop"
android:src="@drawable/ic_open_in_browser_black_24dp" /> android:src="@drawable/ic_open_in_browser_black_24dp" />
</RelativeLayout> </RelativeLayout>
</android.support.constraint.ConstraintLayout> </android.support.constraint.ConstraintLayout>

View File

@ -75,7 +75,7 @@
<string name="prefer_article_viewer_title">"Use the article viewer"</string> <string name="prefer_article_viewer_title">"Use the article viewer"</string>
<string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string> <string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string>
<string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string> <string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string>
<string name="pref_general_category_links">"Link handeling"</string> <string name="pref_general_category_links">"Link handling"</string>
<string name="pref_general_category_displaying">"Displaying"</string> <string name="pref_general_category_displaying">"Displaying"</string>
<string name="pref_general_category_actions">"Actions"</string> <string name="pref_general_category_actions">"Actions"</string>
<string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string> <string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string>
@ -150,7 +150,7 @@
<string name="login_everything_title">Logging every api calls</string> <string name="login_everything_title">Logging every api calls</string>
<string name="login_everything_on">This will log every api call for debug purpose.</string> <string name="login_everything_on">This will log every api call for debug purpose.</string>
<string name="login_everything_off">No api call will be logged</string> <string name="login_everything_off">No api call will be logged</string>
<string name="pref_general_infinite_loading_title">(BETA) Load more articles on scroll</string> <string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Translation</string> <string name="translation">Translation</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string> <string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
<string name="drawer_report_bug">Report a bug</string> <string name="drawer_report_bug">Report a bug</string>

View File

@ -75,7 +75,7 @@
<string name="prefer_article_viewer_title">"Use the article viewer"</string> <string name="prefer_article_viewer_title">"Use the article viewer"</string>
<string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string> <string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string>
<string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string> <string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string>
<string name="pref_general_category_links">"Link handeling"</string> <string name="pref_general_category_links">"Link handling"</string>
<string name="pref_general_category_displaying">"Displaying"</string> <string name="pref_general_category_displaying">"Displaying"</string>
<string name="pref_general_category_actions">"Actions"</string> <string name="pref_general_category_actions">"Actions"</string>
<string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string> <string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string>
@ -150,7 +150,7 @@
<string name="login_everything_title">Logging every api calls</string> <string name="login_everything_title">Logging every api calls</string>
<string name="login_everything_on">This will log every api call for debug purpose.</string> <string name="login_everything_on">This will log every api call for debug purpose.</string>
<string name="login_everything_off">No api call will be logged</string> <string name="login_everything_off">No api call will be logged</string>
<string name="pref_general_infinite_loading_title">(BETA) Load more articles on scroll</string> <string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Translation</string> <string name="translation">Translation</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string> <string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
<string name="drawer_report_bug">Report a bug</string> <string name="drawer_report_bug">Report a bug</string>

View File

@ -75,7 +75,7 @@
<string name="prefer_article_viewer_title">"Use the article viewer"</string> <string name="prefer_article_viewer_title">"Use the article viewer"</string>
<string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string> <string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string>
<string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string> <string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string>
<string name="pref_general_category_links">"Link handeling"</string> <string name="pref_general_category_links">"Link handling"</string>
<string name="pref_general_category_displaying">"Displaying"</string> <string name="pref_general_category_displaying">"Displaying"</string>
<string name="pref_general_category_actions">"Actions"</string> <string name="pref_general_category_actions">"Actions"</string>
<string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string> <string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string>
@ -150,7 +150,7 @@
<string name="login_everything_title">Logging every api calls</string> <string name="login_everything_title">Logging every api calls</string>
<string name="login_everything_on">This will log every api call for debug purpose.</string> <string name="login_everything_on">This will log every api call for debug purpose.</string>
<string name="login_everything_off">No api call will be logged</string> <string name="login_everything_off">No api call will be logged</string>
<string name="pref_general_infinite_loading_title">(BETA) Load more articles on scroll</string> <string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Translation</string> <string name="translation">Translation</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string> <string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
<string name="drawer_report_bug">Report a bug</string> <string name="drawer_report_bug">Report a bug</string>

View File

@ -75,7 +75,7 @@
<string name="prefer_article_viewer_title">"Use the article viewer"</string> <string name="prefer_article_viewer_title">"Use the article viewer"</string>
<string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string> <string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string>
<string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string> <string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string>
<string name="pref_general_category_links">"Link handeling"</string> <string name="pref_general_category_links">"Link handling"</string>
<string name="pref_general_category_displaying">"Displaying"</string> <string name="pref_general_category_displaying">"Displaying"</string>
<string name="pref_general_category_actions">"Actions"</string> <string name="pref_general_category_actions">"Actions"</string>
<string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string> <string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string>
@ -150,7 +150,7 @@
<string name="login_everything_title">Logging every api calls</string> <string name="login_everything_title">Logging every api calls</string>
<string name="login_everything_on">This will log every api call for debug purpose.</string> <string name="login_everything_on">This will log every api call for debug purpose.</string>
<string name="login_everything_off">No api call will be logged</string> <string name="login_everything_off">No api call will be logged</string>
<string name="pref_general_infinite_loading_title">(BETA) Load more articles on scroll</string> <string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Translation</string> <string name="translation">Translation</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string> <string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
<string name="drawer_report_bug">Report a bug</string> <string name="drawer_report_bug">Report a bug</string>

View File

@ -75,7 +75,7 @@
<string name="prefer_article_viewer_title">"Use the article viewer"</string> <string name="prefer_article_viewer_title">"Use the article viewer"</string>
<string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string> <string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string>
<string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string> <string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string>
<string name="pref_general_category_links">"Link handeling"</string> <string name="pref_general_category_links">"Link handling"</string>
<string name="pref_general_category_displaying">"Displaying"</string> <string name="pref_general_category_displaying">"Displaying"</string>
<string name="pref_general_category_actions">"Actions"</string> <string name="pref_general_category_actions">"Actions"</string>
<string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string> <string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string>
@ -150,7 +150,7 @@
<string name="login_everything_title">Logging every api calls</string> <string name="login_everything_title">Logging every api calls</string>
<string name="login_everything_on">This will log every api call for debug purpose.</string> <string name="login_everything_on">This will log every api call for debug purpose.</string>
<string name="login_everything_off">No api call will be logged</string> <string name="login_everything_off">No api call will be logged</string>
<string name="pref_general_infinite_loading_title">(BETA) Load more articles on scroll</string> <string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Translation</string> <string name="translation">Translation</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string> <string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
<string name="drawer_report_bug">Report a bug</string> <string name="drawer_report_bug">Report a bug</string>

View File

@ -150,7 +150,7 @@
<string name="login_everything_title">Logging every api calls</string> <string name="login_everything_title">Logging every api calls</string>
<string name="login_everything_on">This will log every api call for debug purpose.</string> <string name="login_everything_on">This will log every api call for debug purpose.</string>
<string name="login_everything_off">No api call will be logged</string> <string name="login_everything_off">No api call will be logged</string>
<string name="pref_general_infinite_loading_title">(BETA) Load more articles on scroll</string> <string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Übersetzung</string> <string name="translation">Übersetzung</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string> <string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
<string name="drawer_report_bug">Report a bug</string> <string name="drawer_report_bug">Report a bug</string>

View File

@ -75,7 +75,7 @@
<string name="prefer_article_viewer_title">"Use the article viewer"</string> <string name="prefer_article_viewer_title">"Use the article viewer"</string>
<string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string> <string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string>
<string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string> <string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string>
<string name="pref_general_category_links">"Link handeling"</string> <string name="pref_general_category_links">"Link handling"</string>
<string name="pref_general_category_displaying">"Displaying"</string> <string name="pref_general_category_displaying">"Displaying"</string>
<string name="pref_general_category_actions">"Actions"</string> <string name="pref_general_category_actions">"Actions"</string>
<string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string> <string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string>
@ -150,7 +150,7 @@
<string name="login_everything_title">Logging every api calls</string> <string name="login_everything_title">Logging every api calls</string>
<string name="login_everything_on">This will log every api call for debug purpose.</string> <string name="login_everything_on">This will log every api call for debug purpose.</string>
<string name="login_everything_off">No api call will be logged</string> <string name="login_everything_off">No api call will be logged</string>
<string name="pref_general_infinite_loading_title">(BETA) Load more articles on scroll</string> <string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Translation</string> <string name="translation">Translation</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string> <string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
<string name="drawer_report_bug">Report a bug</string> <string name="drawer_report_bug">Report a bug</string>

View File

@ -75,7 +75,7 @@
<string name="prefer_article_viewer_title">"Use the article viewer"</string> <string name="prefer_article_viewer_title">"Use the article viewer"</string>
<string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string> <string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string>
<string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string> <string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string>
<string name="pref_general_category_links">"Link handeling"</string> <string name="pref_general_category_links">"Link handling"</string>
<string name="pref_general_category_displaying">"Displaying"</string> <string name="pref_general_category_displaying">"Displaying"</string>
<string name="pref_general_category_actions">"Actions"</string> <string name="pref_general_category_actions">"Actions"</string>
<string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string> <string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string>
@ -150,7 +150,7 @@
<string name="login_everything_title">Logging every api calls</string> <string name="login_everything_title">Logging every api calls</string>
<string name="login_everything_on">This will log every api call for debug purpose.</string> <string name="login_everything_on">This will log every api call for debug purpose.</string>
<string name="login_everything_off">No api call will be logged</string> <string name="login_everything_off">No api call will be logged</string>
<string name="pref_general_infinite_loading_title">(BETA) Load more articles on scroll</string> <string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Translation</string> <string name="translation">Translation</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string> <string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
<string name="drawer_report_bug">Report a bug</string> <string name="drawer_report_bug">Report a bug</string>

View File

@ -75,7 +75,7 @@
<string name="prefer_article_viewer_title">"Use the article viewer"</string> <string name="prefer_article_viewer_title">"Use the article viewer"</string>
<string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string> <string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string>
<string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string> <string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string>
<string name="pref_general_category_links">"Link handeling"</string> <string name="pref_general_category_links">"Link handling"</string>
<string name="pref_general_category_displaying">"Displaying"</string> <string name="pref_general_category_displaying">"Displaying"</string>
<string name="pref_general_category_actions">"Actions"</string> <string name="pref_general_category_actions">"Actions"</string>
<string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string> <string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string>
@ -150,7 +150,7 @@
<string name="login_everything_title">Logging every api calls</string> <string name="login_everything_title">Logging every api calls</string>
<string name="login_everything_on">This will log every api call for debug purpose.</string> <string name="login_everything_on">This will log every api call for debug purpose.</string>
<string name="login_everything_off">No api call will be logged</string> <string name="login_everything_off">No api call will be logged</string>
<string name="pref_general_infinite_loading_title">(BETA) Load more articles on scroll</string> <string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Translation</string> <string name="translation">Translation</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string> <string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
<string name="drawer_report_bug">Report a bug</string> <string name="drawer_report_bug">Report a bug</string>

View File

@ -150,7 +150,7 @@
<string name="login_everything_title">Log de tous les appels à l\'API</string> <string name="login_everything_title">Log de tous les appels à l\'API</string>
<string name="login_everything_on">Tous les appels à l\'API vont êtres logués</string> <string name="login_everything_on">Tous les appels à l\'API vont êtres logués</string>
<string name="login_everything_off">Aucun appel à l\'API ne sera logué</string> <string name="login_everything_off">Aucun appel à l\'API ne sera logué</string>
<string name="pref_general_infinite_loading_title">(BETA) Charger plus d\'articles au scroll</string> <string name="pref_general_infinite_loading_title">Charger plus d\'articles au scroll</string>
<string name="translation">Traduction</string> <string name="translation">Traduction</string>
<string name="cant_open_invalid_url">Lurl de lélément nest pas valide. En attendant la résolution du problème, le lien ne s\'ouvrira pas.</string> <string name="cant_open_invalid_url">Lurl de lélément nest pas valide. En attendant la résolution du problème, le lien ne s\'ouvrira pas.</string>
<string name="drawer_report_bug">Signaler un bug</string> <string name="drawer_report_bug">Signaler un bug</string>

View File

@ -75,7 +75,7 @@
<string name="prefer_article_viewer_title">"Use the article viewer"</string> <string name="prefer_article_viewer_title">"Use the article viewer"</string>
<string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string> <string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string>
<string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string> <string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string>
<string name="pref_general_category_links">"Link handeling"</string> <string name="pref_general_category_links">"Link handling"</string>
<string name="pref_general_category_displaying">"Displaying"</string> <string name="pref_general_category_displaying">"Displaying"</string>
<string name="pref_general_category_actions">"Actions"</string> <string name="pref_general_category_actions">"Actions"</string>
<string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string> <string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string>
@ -150,7 +150,7 @@
<string name="login_everything_title">Logging every api calls</string> <string name="login_everything_title">Logging every api calls</string>
<string name="login_everything_on">This will log every api call for debug purpose.</string> <string name="login_everything_on">This will log every api call for debug purpose.</string>
<string name="login_everything_off">No api call will be logged</string> <string name="login_everything_off">No api call will be logged</string>
<string name="pref_general_infinite_loading_title">(BETA) Load more articles on scroll</string> <string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Translation</string> <string name="translation">Translation</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string> <string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
<string name="drawer_report_bug">Report a bug</string> <string name="drawer_report_bug">Report a bug</string>

View File

@ -75,7 +75,7 @@
<string name="prefer_article_viewer_title">"Use the article viewer"</string> <string name="prefer_article_viewer_title">"Use the article viewer"</string>
<string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string> <string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string>
<string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string> <string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string>
<string name="pref_general_category_links">"Link handeling"</string> <string name="pref_general_category_links">"Link handling"</string>
<string name="pref_general_category_displaying">"Displaying"</string> <string name="pref_general_category_displaying">"Displaying"</string>
<string name="pref_general_category_actions">"Actions"</string> <string name="pref_general_category_actions">"Actions"</string>
<string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string> <string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string>
@ -150,7 +150,7 @@
<string name="login_everything_title">Logging every api calls</string> <string name="login_everything_title">Logging every api calls</string>
<string name="login_everything_on">This will log every api call for debug purpose.</string> <string name="login_everything_on">This will log every api call for debug purpose.</string>
<string name="login_everything_off">No api call will be logged</string> <string name="login_everything_off">No api call will be logged</string>
<string name="pref_general_infinite_loading_title">(BETA) Load more articles on scroll</string> <string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Translation</string> <string name="translation">Translation</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string> <string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
<string name="drawer_report_bug">Report a bug</string> <string name="drawer_report_bug">Report a bug</string>

View File

@ -75,7 +75,7 @@
<string name="prefer_article_viewer_title">"Use the article viewer"</string> <string name="prefer_article_viewer_title">"Use the article viewer"</string>
<string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string> <string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string>
<string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string> <string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string>
<string name="pref_general_category_links">"Link handeling"</string> <string name="pref_general_category_links">"Link handling"</string>
<string name="pref_general_category_displaying">"Displaying"</string> <string name="pref_general_category_displaying">"Displaying"</string>
<string name="pref_general_category_actions">"Actions"</string> <string name="pref_general_category_actions">"Actions"</string>
<string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string> <string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string>
@ -150,7 +150,7 @@
<string name="login_everything_title">Logging every api calls</string> <string name="login_everything_title">Logging every api calls</string>
<string name="login_everything_on">This will log every api call for debug purpose.</string> <string name="login_everything_on">This will log every api call for debug purpose.</string>
<string name="login_everything_off">No api call will be logged</string> <string name="login_everything_off">No api call will be logged</string>
<string name="pref_general_infinite_loading_title">(BETA) Load more articles on scroll</string> <string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Translation</string> <string name="translation">Translation</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string> <string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
<string name="drawer_report_bug">Report a bug</string> <string name="drawer_report_bug">Report a bug</string>

View File

@ -75,7 +75,7 @@
<string name="prefer_article_viewer_title">"Use the article viewer"</string> <string name="prefer_article_viewer_title">"Use the article viewer"</string>
<string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string> <string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string>
<string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string> <string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string>
<string name="pref_general_category_links">"Link handeling"</string> <string name="pref_general_category_links">"Link handling"</string>
<string name="pref_general_category_displaying">"Displaying"</string> <string name="pref_general_category_displaying">"Displaying"</string>
<string name="pref_general_category_actions">"Actions"</string> <string name="pref_general_category_actions">"Actions"</string>
<string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string> <string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string>
@ -150,7 +150,7 @@
<string name="login_everything_title">Logging every api calls</string> <string name="login_everything_title">Logging every api calls</string>
<string name="login_everything_on">This will log every api call for debug purpose.</string> <string name="login_everything_on">This will log every api call for debug purpose.</string>
<string name="login_everything_off">No api call will be logged</string> <string name="login_everything_off">No api call will be logged</string>
<string name="pref_general_infinite_loading_title">(BETA) Load more articles on scroll</string> <string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Translation</string> <string name="translation">Translation</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string> <string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
<string name="drawer_report_bug">Report a bug</string> <string name="drawer_report_bug">Report a bug</string>

View File

@ -75,7 +75,7 @@
<string name="prefer_article_viewer_title">"Use the article viewer"</string> <string name="prefer_article_viewer_title">"Use the article viewer"</string>
<string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string> <string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string>
<string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string> <string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string>
<string name="pref_general_category_links">"Link handeling"</string> <string name="pref_general_category_links">"Link handling"</string>
<string name="pref_general_category_displaying">"Displaying"</string> <string name="pref_general_category_displaying">"Displaying"</string>
<string name="pref_general_category_actions">"Actions"</string> <string name="pref_general_category_actions">"Actions"</string>
<string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string> <string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string>
@ -150,7 +150,7 @@
<string name="login_everything_title">Logging every api calls</string> <string name="login_everything_title">Logging every api calls</string>
<string name="login_everything_on">This will log every api call for debug purpose.</string> <string name="login_everything_on">This will log every api call for debug purpose.</string>
<string name="login_everything_off">No api call will be logged</string> <string name="login_everything_off">No api call will be logged</string>
<string name="pref_general_infinite_loading_title">(BETA) Load more articles on scroll</string> <string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Translation</string> <string name="translation">Translation</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string> <string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
<string name="drawer_report_bug">Report a bug</string> <string name="drawer_report_bug">Report a bug</string>

View File

@ -150,7 +150,7 @@
<string name="login_everything_title">Logging every api calls</string> <string name="login_everything_title">Logging every api calls</string>
<string name="login_everything_on">This will log every api call for debug purpose.</string> <string name="login_everything_on">This will log every api call for debug purpose.</string>
<string name="login_everything_off">No api call will be logged</string> <string name="login_everything_off">No api call will be logged</string>
<string name="pref_general_infinite_loading_title">(BETA) Load more articles on scroll</string> <string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Vertaling</string> <string name="translation">Vertaling</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string> <string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
<string name="drawer_report_bug">Report a bug</string> <string name="drawer_report_bug">Report a bug</string>

View File

@ -75,7 +75,7 @@
<string name="prefer_article_viewer_title">"Use the article viewer"</string> <string name="prefer_article_viewer_title">"Use the article viewer"</string>
<string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string> <string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string>
<string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string> <string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string>
<string name="pref_general_category_links">"Link handeling"</string> <string name="pref_general_category_links">"Link handling"</string>
<string name="pref_general_category_displaying">"Displaying"</string> <string name="pref_general_category_displaying">"Displaying"</string>
<string name="pref_general_category_actions">"Actions"</string> <string name="pref_general_category_actions">"Actions"</string>
<string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string> <string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string>
@ -150,7 +150,7 @@
<string name="login_everything_title">Logging every api calls</string> <string name="login_everything_title">Logging every api calls</string>
<string name="login_everything_on">This will log every api call for debug purpose.</string> <string name="login_everything_on">This will log every api call for debug purpose.</string>
<string name="login_everything_off">No api call will be logged</string> <string name="login_everything_off">No api call will be logged</string>
<string name="pref_general_infinite_loading_title">(BETA) Load more articles on scroll</string> <string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Translation</string> <string name="translation">Translation</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string> <string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
<string name="drawer_report_bug">Report a bug</string> <string name="drawer_report_bug">Report a bug</string>

View File

@ -75,7 +75,7 @@
<string name="prefer_article_viewer_title">"Use the article viewer"</string> <string name="prefer_article_viewer_title">"Use the article viewer"</string>
<string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string> <string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string>
<string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string> <string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string>
<string name="pref_general_category_links">"Link handeling"</string> <string name="pref_general_category_links">"Link handling"</string>
<string name="pref_general_category_displaying">"Displaying"</string> <string name="pref_general_category_displaying">"Displaying"</string>
<string name="pref_general_category_actions">"Actions"</string> <string name="pref_general_category_actions">"Actions"</string>
<string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string> <string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string>
@ -150,7 +150,7 @@
<string name="login_everything_title">Logging every api calls</string> <string name="login_everything_title">Logging every api calls</string>
<string name="login_everything_on">This will log every api call for debug purpose.</string> <string name="login_everything_on">This will log every api call for debug purpose.</string>
<string name="login_everything_off">No api call will be logged</string> <string name="login_everything_off">No api call will be logged</string>
<string name="pref_general_infinite_loading_title">(BETA) Load more articles on scroll</string> <string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Translation</string> <string name="translation">Translation</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string> <string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
<string name="drawer_report_bug">Report a bug</string> <string name="drawer_report_bug">Report a bug</string>

View File

@ -150,7 +150,7 @@
<string name="login_everything_title">Registrando todas as chamadas a api</string> <string name="login_everything_title">Registrando todas as chamadas a api</string>
<string name="login_everything_on">Isso registrará todas as chamadas api para fins de depuração.</string> <string name="login_everything_on">Isso registrará todas as chamadas api para fins de depuração.</string>
<string name="login_everything_off">Nenhuma chamada a api será registrada</string> <string name="login_everything_off">Nenhuma chamada a api será registrada</string>
<string name="pref_general_infinite_loading_title">(BETA) Carregar mais artigos ao realizar o scroll</string> <string name="pref_general_infinite_loading_title">Carregar mais artigos ao realizar o scroll</string>
<string name="translation">Traduções</string> <string name="translation">Traduções</string>
<string name="cant_open_invalid_url">A url está inválida. Estou tentando resolver esse problema para que o aplicativo não encerre.</string> <string name="cant_open_invalid_url">A url está inválida. Estou tentando resolver esse problema para que o aplicativo não encerre.</string>
<string name="drawer_report_bug">Reportar erro</string> <string name="drawer_report_bug">Reportar erro</string>

View File

@ -75,7 +75,7 @@
<string name="prefer_article_viewer_title">"Use the article viewer"</string> <string name="prefer_article_viewer_title">"Use the article viewer"</string>
<string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string> <string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string>
<string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string> <string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string>
<string name="pref_general_category_links">"Link handeling"</string> <string name="pref_general_category_links">"Link handling"</string>
<string name="pref_general_category_displaying">"Displaying"</string> <string name="pref_general_category_displaying">"Displaying"</string>
<string name="pref_general_category_actions">"Actions"</string> <string name="pref_general_category_actions">"Actions"</string>
<string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string> <string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string>
@ -150,7 +150,7 @@
<string name="login_everything_title">Logging every api calls</string> <string name="login_everything_title">Logging every api calls</string>
<string name="login_everything_on">This will log every api call for debug purpose.</string> <string name="login_everything_on">This will log every api call for debug purpose.</string>
<string name="login_everything_off">No api call will be logged</string> <string name="login_everything_off">No api call will be logged</string>
<string name="pref_general_infinite_loading_title">(BETA) Load more articles on scroll</string> <string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Translation</string> <string name="translation">Translation</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string> <string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
<string name="drawer_report_bug">Report a bug</string> <string name="drawer_report_bug">Report a bug</string>

View File

@ -75,7 +75,7 @@
<string name="prefer_article_viewer_title">"Use the article viewer"</string> <string name="prefer_article_viewer_title">"Use the article viewer"</string>
<string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string> <string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string>
<string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string> <string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string>
<string name="pref_general_category_links">"Link handeling"</string> <string name="pref_general_category_links">"Link handling"</string>
<string name="pref_general_category_displaying">"Displaying"</string> <string name="pref_general_category_displaying">"Displaying"</string>
<string name="pref_general_category_actions">"Actions"</string> <string name="pref_general_category_actions">"Actions"</string>
<string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string> <string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string>
@ -150,7 +150,7 @@
<string name="login_everything_title">Logging every api calls</string> <string name="login_everything_title">Logging every api calls</string>
<string name="login_everything_on">This will log every api call for debug purpose.</string> <string name="login_everything_on">This will log every api call for debug purpose.</string>
<string name="login_everything_off">No api call will be logged</string> <string name="login_everything_off">No api call will be logged</string>
<string name="pref_general_infinite_loading_title">(BETA) Load more articles on scroll</string> <string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Translation</string> <string name="translation">Translation</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string> <string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
<string name="drawer_report_bug">Report a bug</string> <string name="drawer_report_bug">Report a bug</string>

View File

@ -75,7 +75,7 @@
<string name="prefer_article_viewer_title">"Use the article viewer"</string> <string name="prefer_article_viewer_title">"Use the article viewer"</string>
<string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string> <string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string>
<string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string> <string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string>
<string name="pref_general_category_links">"Link handeling"</string> <string name="pref_general_category_links">"Link handling"</string>
<string name="pref_general_category_displaying">"Displaying"</string> <string name="pref_general_category_displaying">"Displaying"</string>
<string name="pref_general_category_actions">"Actions"</string> <string name="pref_general_category_actions">"Actions"</string>
<string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string> <string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string>
@ -150,7 +150,7 @@
<string name="login_everything_title">Logging every api calls</string> <string name="login_everything_title">Logging every api calls</string>
<string name="login_everything_on">This will log every api call for debug purpose.</string> <string name="login_everything_on">This will log every api call for debug purpose.</string>
<string name="login_everything_off">No api call will be logged</string> <string name="login_everything_off">No api call will be logged</string>
<string name="pref_general_infinite_loading_title">(BETA) Load more articles on scroll</string> <string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Translation</string> <string name="translation">Translation</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string> <string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
<string name="drawer_report_bug">Report a bug</string> <string name="drawer_report_bug">Report a bug</string>

View File

@ -75,7 +75,7 @@
<string name="prefer_article_viewer_title">"Use the article viewer"</string> <string name="prefer_article_viewer_title">"Use the article viewer"</string>
<string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string> <string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string>
<string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string> <string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string>
<string name="pref_general_category_links">"Link handeling"</string> <string name="pref_general_category_links">"Link handling"</string>
<string name="pref_general_category_displaying">"Displaying"</string> <string name="pref_general_category_displaying">"Displaying"</string>
<string name="pref_general_category_actions">"Actions"</string> <string name="pref_general_category_actions">"Actions"</string>
<string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string> <string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string>
@ -150,7 +150,7 @@
<string name="login_everything_title">Logging every api calls</string> <string name="login_everything_title">Logging every api calls</string>
<string name="login_everything_on">This will log every api call for debug purpose.</string> <string name="login_everything_on">This will log every api call for debug purpose.</string>
<string name="login_everything_off">No api call will be logged</string> <string name="login_everything_off">No api call will be logged</string>
<string name="pref_general_infinite_loading_title">(BETA) Load more articles on scroll</string> <string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Translation</string> <string name="translation">Translation</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string> <string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
<string name="drawer_report_bug">Report a bug</string> <string name="drawer_report_bug">Report a bug</string>

View File

@ -75,7 +75,7 @@
<string name="prefer_article_viewer_title">"Use the article viewer"</string> <string name="prefer_article_viewer_title">"Use the article viewer"</string>
<string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string> <string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string>
<string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string> <string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string>
<string name="pref_general_category_links">"Link handeling"</string> <string name="pref_general_category_links">"Link handling"</string>
<string name="pref_general_category_displaying">"Displaying"</string> <string name="pref_general_category_displaying">"Displaying"</string>
<string name="pref_general_category_actions">"Actions"</string> <string name="pref_general_category_actions">"Actions"</string>
<string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string> <string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string>
@ -150,7 +150,7 @@
<string name="login_everything_title">Logging every api calls</string> <string name="login_everything_title">Logging every api calls</string>
<string name="login_everything_on">This will log every api call for debug purpose.</string> <string name="login_everything_on">This will log every api call for debug purpose.</string>
<string name="login_everything_off">No api call will be logged</string> <string name="login_everything_off">No api call will be logged</string>
<string name="pref_general_infinite_loading_title">(BETA) Load more articles on scroll</string> <string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Translation</string> <string name="translation">Translation</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string> <string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
<string name="drawer_report_bug">Report a bug</string> <string name="drawer_report_bug">Report a bug</string>

View File

@ -75,7 +75,7 @@
<string name="prefer_article_viewer_title">"Use the article viewer"</string> <string name="prefer_article_viewer_title">"Use the article viewer"</string>
<string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string> <string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string>
<string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string> <string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string>
<string name="pref_general_category_links">"Link handeling"</string> <string name="pref_general_category_links">"Link handling"</string>
<string name="pref_general_category_displaying">"Displaying"</string> <string name="pref_general_category_displaying">"Displaying"</string>
<string name="pref_general_category_actions">"Actions"</string> <string name="pref_general_category_actions">"Actions"</string>
<string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string> <string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string>
@ -150,7 +150,7 @@
<string name="login_everything_title">Logging every api calls</string> <string name="login_everything_title">Logging every api calls</string>
<string name="login_everything_on">This will log every api call for debug purpose.</string> <string name="login_everything_on">This will log every api call for debug purpose.</string>
<string name="login_everything_off">No api call will be logged</string> <string name="login_everything_off">No api call will be logged</string>
<string name="pref_general_infinite_loading_title">(BETA) Load more articles on scroll</string> <string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Translation</string> <string name="translation">Translation</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string> <string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
<string name="drawer_report_bug">Report a bug</string> <string name="drawer_report_bug">Report a bug</string>

View File

@ -75,7 +75,7 @@
<string name="prefer_article_viewer_title">"Use the article viewer"</string> <string name="prefer_article_viewer_title">"Use the article viewer"</string>
<string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string> <string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string>
<string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string> <string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string>
<string name="pref_general_category_links">"Link handeling"</string> <string name="pref_general_category_links">"Link handling"</string>
<string name="pref_general_category_displaying">"Displaying"</string> <string name="pref_general_category_displaying">"Displaying"</string>
<string name="pref_general_category_actions">"Actions"</string> <string name="pref_general_category_actions">"Actions"</string>
<string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string> <string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string>
@ -150,7 +150,7 @@
<string name="login_everything_title">Logging every api calls</string> <string name="login_everything_title">Logging every api calls</string>
<string name="login_everything_on">This will log every api call for debug purpose.</string> <string name="login_everything_on">This will log every api call for debug purpose.</string>
<string name="login_everything_off">No api call will be logged</string> <string name="login_everything_off">No api call will be logged</string>
<string name="pref_general_infinite_loading_title">(BETA) Load more articles on scroll</string> <string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Translation</string> <string name="translation">Translation</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string> <string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
<string name="drawer_report_bug">Report a bug</string> <string name="drawer_report_bug">Report a bug</string>

View File

@ -1,161 +1,161 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!--Generated by crowdin.com--> <!--Generated by crowdin.com-->
<resources xmlns:tools="http://schemas.android.com/tools"> <resources xmlns:tools="http://schemas.android.com/tools">
<string name="app_name">"Reader for Selfoss"</string> <string name="app_name">"Selfoss 阅读器"</string>
<string name="title_activity_login">"Log in"</string> <string name="title_activity_login">"登录"</string>
<string name="prompt_password">"Password"</string> <string name="prompt_password">"密码"</string>
<string name="prompt_http_password">"HTTP Password"</string> <string name="prompt_http_password">"HTTP 密码"</string>
<string name="action_sign_in">"Go"</string> <string name="action_sign_in">"转至"</string>
<string name="error_invalid_password">"Password not long enough"</string> <string name="error_invalid_password">"密码不够长"</string>
<string name="error_field_required">"Field required"</string> <string name="error_field_required">"必填字段"</string>
<string name="prompt_url">"Url"</string> <string name="prompt_url">"网址"</string>
<string name="withLoginSwitch">"Login required ?"</string> <string name="withLoginSwitch">"需要登录?"</string>
<string name="withHttpLoginSwitch">"HTTP Login required ?"</string> <string name="withHttpLoginSwitch">"请先登录网站"</string>
<string name="login_url_problem">"Oops. You may need to add a \"/\" at the end of the url."</string> <string name="login_url_problem">"哎呀。您可能需要在网址的末尾添加一个 \"/\"。"</string>
<string name="prompt_login">"Username"</string> <string name="prompt_login">"用户名"</string>
<string name="prompt_http_login">"HTTP Username"</string> <string name="prompt_http_login">"HTTP 用户名"</string>
<string name="label_share">"Share"</string> <string name="label_share">"分享"</string>
<string name="readAll">"Read all"</string> <string name="readAll">"全部阅读"</string>
<string name="action_disconnect">"Disconnect"</string> <string name="action_disconnect">"断开连接"</string>
<string name="title_activity_settings">"Settings"</string> <string name="title_activity_settings">"设置"</string>
<string name="pref_header_general">"General"</string> <string name="pref_header_general">"常用"</string>
<string name="pref_switch_actions_tap_title">"Tap action on the articles"</string> <string name="pref_switch_actions_tap_title">"点击要对文章进行的操作"</string>
<string name="add_source_hint_tags">"Tag1, Tag2, Tag3"</string> <string name="add_source_hint_tags">"标签1,标签2,标签3"</string>
<string name="add_source_hint_url">"Link"</string> <string name="add_source_hint_url">"链接"</string>
<string name="add_source_hint_name">"Name"</string> <string name="add_source_hint_name">"名称"</string>
<string name="add_source">"Add a source"</string> <string name="add_source">"添加新源"</string>
<string name="add_source_save">"Save"</string> <string name="add_source_save">"保存"</string>
<string name="wrong_infos">"Check your details again."</string> <string name="wrong_infos">"再次检查您的详细信息。"</string>
<string name="all_posts_not_read">"All posts weren't read"</string> <string name="all_posts_not_read">"所有帖子都未读"</string>
<string name="all_posts_read">"All posts were read"</string> <string name="all_posts_read">"所有帖子已读"</string>
<string name="cant_get_favs">"Can't get favorites"</string> <string name="cant_get_favs">"无法获取收藏文件"</string>
<string name="cant_get_new_elements">"Can't get new articles"</string> <string name="cant_get_new_elements">"无法获取新文章"</string>
<string name="cant_get_read">"Can't get read articles"</string> <string name="cant_get_read">"无法获取已读文章"</string>
<string name="nothing_here">"Nothing here"</string> <string name="nothing_here">"暂无内容!"</string>
<string name="tab_new">"New"</string> <string name="tab_new">"新建"</string>
<string name="tab_read">"All"</string> <string name="tab_read">"所有"</string>
<string name="tab_favs">"Favorites"</string> <string name="tab_favs">"收藏夹"</string>
<string name="action_about">"About"</string> <string name="action_about">"关于我们"</string>
<string name="marked_as_read">"Item read"</string> <string name="marked_as_read">"已读"</string>
<string name="undo_string">"Undo"</string> <string name="undo_string">"撤销"</string>
<string name="addStringNoUrl">"Log in to add sources."</string> <string name="addStringNoUrl">"登录以添加数据源。"</string>
<string name="cant_get_sources">"Can't get sources list."</string> <string name="cant_get_sources">"无法获取数据列表。"</string>
<string name="cant_create_source">"Can't create source."</string> <string name="cant_create_source">"无法创建源数据。"</string>
<string name="cant_get_spouts">"Can't get spouts list."</string> <string name="cant_get_spouts">"无法获取数据列表"</string>
<string name="form_not_complete">"The form is not complete"</string> <string name="form_not_complete">"窗体未完成"</string>
<string name="pref_header_links">"Links"</string> <string name="pref_header_links">"链接"</string>
<string name="issue_tracker_link">"Issue Tracker"</string> <string name="issue_tracker_link">"问题追踪器"</string>
<string name="issue_tracker_summary">"Report a bug or ask for a new feature"</string> <string name="issue_tracker_summary">"报告错误或请求新功能"</string>
<string name="warning_wrong_url">"WARNING"</string> <string name="warning_wrong_url">"警告"</string>
<string name="pref_switch_card_view_title">"Card View"</string> <string name="pref_switch_card_view_title">"卡片视图"</string>
<string name="cant_mark_favortie">"Can't mark article as favorite"</string> <string name="cant_mark_favortie">"不能将文章标记为收藏"</string>
<string name="cant_unmark_favortie">"Can't remove item from favorite"</string> <string name="cant_unmark_favortie">"无法从收藏中删除项目"</string>
<string name="share">"Share"</string> <string name="share">"共享"</string>
<string name="rating_prompt_title">"Enjoying the app ?"</string> <string name="rating_prompt_title">"喜欢这个应用吗?"</string>
<string name="rating_prompt_yes">"Yes !"</string> <string name="rating_prompt_yes">"是的!"</string>
<string name="rating_prompt_no">"Not really …"</string> <string name="rating_prompt_no">"不大喜欢…"</string>
<string name="rating_prompt_feedback_title">"Can you tell us why ?"</string> <string name="rating_prompt_feedback_title">"能告诉我们为什么吗?"</string>
<string name="rating_prompt_feedback_yes">"OK !"</string> <string name="rating_prompt_feedback_yes">"好的!"</string>
<string name="rating_prompt_feedback_no">"Not now."</string> <string name="rating_prompt_feedback_no">"以后再说。"</string>
<string name="rating_prompt_rating_title">"Great ! Can you rate us on the Store ?"</string> <string name="rating_prompt_rating_title">"太好了!你能在应用商店给我们打分吗?"</string>
<string name="rating_prompt_rating_yes">"Sure !"</string> <string name="rating_prompt_rating_yes">"当然!"</string>
<string name="rating_prompt_rating_no">"Not right now."</string> <string name="rating_prompt_rating_no">"现在不行。"</string>
<string name="rating_prompt_thanks">"Thanks, your feedback help enhance the app !"</string> <string name="rating_prompt_thanks">"谢谢,您的反馈有助于改进应用程序!"</string>
<string name="switch_unread_count">"Display the unread count as a badge for the bottom bar."</string> <string name="switch_unread_count">"将未读数在底部显示为一个徽标。"</string>
<string name="switch_unread_count_title">"Display unread count"</string> <string name="switch_unread_count_title">"显示未读数"</string>
<string name="display_all_counts_title">"Display count for favorite and read"</string> <string name="display_all_counts_title">"显示收藏和已读的计数"</string>
<string name="menu_share_the_app">"Invite friends"</string> <string name="menu_share_the_app">"邀请朋友"</string>
<string name="invitation_title">"Try this app for your Selfoss RSS feeds !"</string> <string name="invitation_title">"尝试用这个应用来阅读你的 Selfoss RSS 源!"</string>
<string name="invitation_message">"I use this app for my Selfoss RSS feeds. You may like it too !"</string> <string name="invitation_message">"我用这个应用来阅读我的 Selfoss RSS 源。你也会喜欢的!"</string>
<string name="invitation_cta">"Try the app"</string> <string name="invitation_cta">"试用应用程序"</string>
<string name="text_wrong_url">"You seem to be trying to use an invalid URL. Make sure it is correct, and if the problem persists, contact me (via the store contact link). Please note that the app needs you to be using Selfoss. You can't access RSS feeds without it."</string> <string name="text_wrong_url">"您似乎试图使用无效的 URL。确保它是正确的如果问题仍然存在请与我联系 (通过商店的联系链接)。请注意,该应用程序需要您使用 Selfoss。没有它您无法访问 RSS 源。"</string>
<string name="pref_general_internal_browser_title">"Open links inside the app"</string> <string name="pref_general_internal_browser_title">"打开应用程序中的链接"</string>
<string name="pref_general_internal_browser_on">"Articles will open inside the app"</string> <string name="pref_general_internal_browser_on">"文章将在应用程序内打开"</string>
<string name="pref_general_internal_browser_off">"Articles will open with your default browser"</string> <string name="pref_general_internal_browser_off">"文章将使用默认浏览器打开"</string>
<string name="prefer_article_viewer_title">"Use the article viewer"</string> <string name="prefer_article_viewer_title">"使用文章查看器"</string>
<string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string> <string name="prefer_article_viewer_on">"将使用文章查看器而不是内部浏览器"</string>
<string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string> <string name="prefer_article_viewer_off">"将使用内部浏览器而不是文章查看器"</string>
<string name="pref_general_category_links">"Link handeling"</string> <string name="pref_general_category_links">"链接处理"</string>
<string name="pref_general_category_displaying">"Displaying"</string> <string name="pref_general_category_displaying">"显示"</string>
<string name="pref_general_category_actions">"Actions"</string> <string name="pref_general_category_actions">"操作"</string>
<string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string> <string name="pref_switch_card_view_on">"这些文章将以卡片形式显示"</string>
<string name="pref_switch_card_view_off">"The articles will be displayed as a list"</string> <string name="pref_switch_card_view_off">"这些文章将以列表形式显示"</string>
<string name="pref_switch_actions_tap_on">"Displays the action bar under the article"</string> <string name="pref_switch_actions_tap_on">"在文章下方显示操作栏"</string>
<string name="pref_switch_actions_tap_off">"When selecting an article it will open in your selected browser"</string> <string name="pref_switch_actions_tap_off">"选择文章时,将会在你选择的浏览器中打开"</string>
<string name="menu_home_refresh">"Update remote"</string> <string name="menu_home_refresh">"更新远程"</string>
<string name="refresh_success_response">"The remote is updated, you can now reload the articles list"</string> <string name="refresh_success_response">"远程已更新,您现在可以重新加载文章列表"</string>
<string name="refresh_failer_message">"The update didn't work, try again later, or check your selfoss logs."</string> <string name="refresh_failer_message">"更新未执行,请稍后重试,或者查看 Selfoss 日志。"</string>
<string name="refresh_in_progress">"Refresh in progress"</string> <string name="refresh_in_progress">"正在进行刷新"</string>
<string name="new_apk_available_title">"A new APK is available."</string> <string name="new_apk_available_title">"一个新的安装包可用。"</string>
<string name="new_apk_available_message">"A new APK is available to download on the official repository."</string> <string name="new_apk_available_message">"一个新的安装包可以从官方资料库下载了。"</string>
<string name="new_apk_available_get">"Download now"</string> <string name="new_apk_available_get">"立即下载"</string>
<string name="new_apk_available_no">"Ignore version"</string> <string name="new_apk_available_no">"忽略该版本"</string>
<string name="intro_hello_title">"Hi there !"</string> <string name="intro_hello_title">"嘿,你好!"</string>
<string name="intro_hello_message">"Thanks for downloading the app !"</string> <string name="intro_hello_message">"感谢下载应用程序!"</string>
<string name="intro_needs_selfoss_title">"Before you start…"</string> <string name="intro_needs_selfoss_title">"在开始之前…"</string>
<string name="intro_needs_selfoss_message">"You can't use the app without a Selfoss instance."</string> <string name="intro_needs_selfoss_message">"您不能在没有 Selfoss 运行的情况下使用这个应用。"</string>
<string name="intro_needs_selfoss_link">"What is Selfoss ?"</string> <string name="intro_needs_selfoss_link">"什么是 Selfoss"</string>
<string name="intro_all_set_title">"All set !"</string> <string name="intro_all_set_title">"都好了!"</string>
<string name="intro_all_set_message">"You are ready to use the app. Don't forget to go to the settings page to configure your app, and where you'll find some useful links."</string> <string name="intro_all_set_message">"你已准备就绪。别忘了去设置页面配置应用,可以还可以在那里找到一些有用的链接。"</string>
<string name="card_height_title">Full height cards</string> <string name="card_height_title">全高卡片</string>
<string name="card_height_on">Cards height will adjust to its content</string> <string name="card_height_on">卡片高度将根据内容调整</string>
<string name="card_height_off">Card height will be fixed</string> <string name="card_height_off">卡片高度将被固定</string>
<string name="source_code">Source code</string> <string name="source_code">源代码</string>
<string name="cant_mark_read">Can\'t mark article as read</string> <string name="cant_mark_read">无法将文章标记为已读</string>
<string name="drawer_error_loading_tags">Error loading tags…</string> <string name="drawer_error_loading_tags">加载标记时出错..。</string>
<string name="drawer_error_loading_sources">Error loading sources…</string> <string name="drawer_error_loading_sources">加载源时出错..。</string>
<string name="drawer_item_filters">Filters</string> <string name="drawer_item_filters">搜索条件</string>
<string name="drawer_action_clear">clear</string> <string name="drawer_action_clear">清空</string>
<string name="drawer_item_tags">Tags</string> <string name="drawer_item_tags">标签</string>
<string name="drawer_item_sources">Sources</string> <string name="drawer_item_sources">来源</string>
<string name="drawer_action_edit">edit</string> <string name="drawer_action_edit">编辑</string>
<string name="cache_drawer_error" tools:keep="@string/cache_drawer_error">Couldn\'t cache your drawer data</string> <string name="cache_drawer_error" tools:keep="@string/cache_drawer_error">无法缓存您的抽屉数据</string>
<string name="no_tags_loaded">No tags loaded</string> <string name="no_tags_loaded">未加载标签</string>
<string name="no_sources_loaded">No sources loaded</string> <string name="no_sources_loaded">未加载数据源</string>
<string name="drawer_loading">Loading </string> <string name="drawer_loading">正在载入</string>
<string name="menu_home_search">Search</string> <string name="menu_home_search">搜索</string>
<string name="can_delete_source">Can\'t delete the source</string> <string name="can_delete_source">无法删除数据源</string>
<string name="base_url_error">There was an issue when trying to communicate with your Selfoss Instance. If the issue persists, please get in touch with me.</string> <string name="base_url_error">与您的 Selfoss 通信时出现问题。如果问题一直存在,请与我联系。</string>
<string name="pref_header_theme">Themes</string> <string name="pref_header_theme">主题</string>
<string name="default_theme">Default</string> <string name="default_theme">默认​​​​​</string>
<string name="teal_orange_theme">Teal/Orange/Light</string> <string name="teal_orange_theme">蓝绿色/橙色/浅色</string>
<string name="cyan_pink_theme">Cyan/Pink/Light</string> <string name="cyan_pink_theme">青色/粉红色/浅色</string>
<string name="grey_orange_theme">Grey/Orange/Light</string> <string name="grey_orange_theme">灰色/橙色/浅色</string>
<string name="blue_amber_theme">Blue/Amber/Light</string> <string name="blue_amber_theme">蓝色/琥珀色/浅色</string>
<string name="indigo_pink_theme">Indigo/Pink/Light</string> <string name="indigo_pink_theme">靛蓝/粉红色/浅色</string>
<string name="red_teal_theme">Red/Teal/Light</string> <string name="red_teal_theme">红/蓝/光</string>
<string name="teal_orange_dark_theme">Teal/Orange/Dark</string> <string name="teal_orange_dark_theme">青色/橙色/深色</string>
<string name="cyan_pink_dark_theme">Cyan/Pink/Dark</string> <string name="cyan_pink_dark_theme">青色/粉红色/深色</string>
<string name="default_dark_theme">Default/Dark</string> <string name="default_dark_theme">默认值/暗</string>
<string name="grey_orange_dark_theme">Grey/Orange/Dark</string> <string name="grey_orange_dark_theme">灰色/橙色/暗色</string>
<string name="blue_amber_dark_theme">Blue/Amber/Dark</string> <string name="blue_amber_dark_theme">蓝色/琥珀色/暗色</string>
<string name="indigo_pink_dark_theme">Indigo/Pink/Dark</string> <string name="indigo_pink_dark_theme">靛蓝/粉红色/暗色</string>
<string name="red_teal_dark_theme">Red/Teal/Dark</string> <string name="red_teal_dark_theme">红/蓝/暗</string>
<string name="pref_header_debug">Debug</string> <string name="pref_header_debug">调试</string>
<string name="login_debug_title">Activate to log login errors</string> <string name="login_debug_title">激活以记录登录错误</string>
<string name="login_debug_on">Any error on the login page will be logged</string> <string name="login_debug_on">登录页上的任何错误都将被记录</string>
<string name="login_debug_off">No log on the login page</string> <string name="login_debug_off">登录页上没有记录</string>
<string name="login_menu_debug">Debug</string> <string name="login_menu_debug">调试</string>
<string name="self_hosted_cert_switch">Using a self hosted certificate ?</string> <string name="self_hosted_cert_switch">使用自托管证书?</string>
<string name="self_signed_cert_warning">Due to security reasons, self signed certificates are not supported by default. By activating this, I\'ll not be responsible of any security problem you encounter.</string> <string name="self_signed_cert_warning">出于安全考虑, 默认情况下不支持自签名证书。如果激活此项, 您遇到的任何安全问题我将概不负责。</string>
<string name="pref_selfoss_category">Selfoss Api</string> <string name="pref_selfoss_category">塞尔福斯 Api</string>
<string name="pref_api_items_number_title">Loaded items number</string> <string name="pref_api_items_number_title">已加载项目编号</string>
<string name="read_debug_title">Read articles appearing as unread ?</string> <string name="read_debug_title">已读文章显示为未读?</string>
<string name="read_debug_off">No log when marking an item as read</string> <string name="read_debug_off">将项目标记为已读时没有记录</string>
<string name="read_debug_on">Api calls will be logged when marking an article as read</string> <string name="read_debug_on">将项目标记为已读时将记录 Api 调用</string>
<string name="summary_debug_identifier">Debug identifier</string> <string name="summary_debug_identifier">除错标识符</string>
<string name="unique_id_to_clipboard">Identifier copied to your clipboard</string> <string name="unique_id_to_clipboard">复制到你的剪贴板的标识符</string>
<string name="display_header_drawer_summary">Display a header with the selfoss instance url on the lateral drawer.</string> <string name="display_header_drawer_summary">在侧边栏中显示带有 Selfoss 链接地址的页眉。</string>
<string name="display_header_drawer_title">Account header</string> <string name="display_header_drawer_title">帐户页眉</string>
<string name="login_everything_title">Logging every api calls</string> <string name="login_everything_title">记录每个 api 调用</string>
<string name="login_everything_on">This will log every api call for debug purpose.</string> <string name="login_everything_on">为了程序除错,这将记录每个 api 调用</string>
<string name="login_everything_off">No api call will be logged</string> <string name="login_everything_off">将不记录任何 api 调用</string>
<string name="pref_general_infinite_loading_title">(BETA) Load more articles on scroll</string> <string name="pref_general_infinite_loading_title">翻页时载入更多文章</string>
<string name="translation">Translation</string> <string name="translation">翻译</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string> <string name="cant_open_invalid_url">项目链接地址无效。我正在设法解决这个问题,以避免应用程序崩溃。</string>
<string name="drawer_report_bug">Report a bug</string> <string name="drawer_report_bug">报告错误</string>
<string name="items_number_should_be_number">The items number should be an integer.</string> <string name="items_number_should_be_number">项目数应为整数。</string>
<string name="reader_action_more">Read more</string> <string name="reader_action_more">阅读更多</string>
<string name="reader_action_open">Open in browser</string> <string name="reader_action_open">在浏览器中打开</string>
<string name="reader_action_share">Share</string> <string name="reader_action_share">分享</string>
</resources> </resources>

View File

@ -73,7 +73,7 @@
<string name="prefer_article_viewer_title">"Use the article viewer"</string> <string name="prefer_article_viewer_title">"Use the article viewer"</string>
<string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string> <string name="prefer_article_viewer_on">"Will use the article viewer instead of the internal browser"</string>
<string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string> <string name="prefer_article_viewer_off">"Will use the internal browser instead of the article viewer"</string>
<string name="pref_general_category_links">"Link handeling"</string> <string name="pref_general_category_links">"Link handling"</string>
<string name="pref_general_category_displaying">"Displaying"</string> <string name="pref_general_category_displaying">"Displaying"</string>
<string name="pref_general_category_actions">"Actions"</string> <string name="pref_general_category_actions">"Actions"</string>
<string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string> <string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string>
@ -151,7 +151,7 @@
<string name="login_everything_title">Logging every api calls</string> <string name="login_everything_title">Logging every api calls</string>
<string name="login_everything_on">This will log every api call for debug purpose.</string> <string name="login_everything_on">This will log every api call for debug purpose.</string>
<string name="login_everything_off">No api call will be logged</string> <string name="login_everything_off">No api call will be logged</string>
<string name="pref_general_infinite_loading_title">(BETA) Load more articles on scroll</string> <string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Translation</string> <string name="translation">Translation</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string> <string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
<string name="report_github_user" translatable="false">aminecmi</string> <string name="report_github_user" translatable="false">aminecmi</string>