Kotlin style guide. (#125)

This commit is contained in:
Amine Bou 2017-11-25 13:12:12 +01:00 committed by GitHub
parent 68098f4d84
commit 69ac2e2b44
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 1150 additions and 703 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -24,7 +24,6 @@ import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class AddSourceActivity : AppCompatActivity() {
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>()
spoutsSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(adapterView: AdapterView<*>, view: View, i: Int, l: Long) {
@ -82,7 +86,10 @@ class AddSourceActivity : AppCompatActivity() {
var items: 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) {
items = response.body()!!
@ -98,10 +105,10 @@ class AddSourceActivity : AppCompatActivity() {
ArrayAdapter(
this@AddSourceActivity,
android.R.layout.simple_spinner_item,
itemsStrings)
itemsStrings
)
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spoutsSpinner.adapter = spinnerArrayAdapter
} else {
handleProblemWithSpouts()
}
@ -112,13 +119,21 @@ class AddSourceActivity : AppCompatActivity() {
}
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
}
})
}
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) {
sourceUri.setText(intent.getStringExtra(Intent.EXTRA_TEXT))
nameInput.setText(intent.getStringExtra(Intent.EXTRA_TITLE))
@ -146,16 +161,27 @@ class AddSourceActivity : AppCompatActivity() {
tags.text.toString(),
""
).enqueue(object : Callback<SuccessResponse> {
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {
override fun onResponse(
call: Call<SuccessResponse>,
response: Response<SuccessResponse>
) {
if (response.body() != null && response.body()!!.isSuccess) {
finish()
} 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) {
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,7 +11,11 @@ import android.os.Bundle
import android.preference.PreferenceManager
import android.support.v4.view.MenuItemCompat
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.*
import android.support.v7.widget.DividerItemDecoration
import android.support.v7.widget.GridLayoutManager
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.SearchView
import android.support.v7.widget.StaggeredGridLayoutManager
import android.support.v7.widget.helper.ItemTouchHelper
import android.view.Menu
import android.view.MenuItem
@ -63,10 +67,10 @@ import com.mikepenz.materialdrawer.holder.BadgeStyle
import com.mikepenz.materialdrawer.model.DividerDrawerItem
import com.mikepenz.materialdrawer.model.PrimaryDrawerItem
import com.mikepenz.materialdrawer.model.SecondaryDrawerItem
import kotlinx.android.synthetic.main.activity_home.*
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import kotlinx.android.synthetic.main.activity_home.*
class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
@ -117,13 +121,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
private var badgeAll: Int = -1
private var badgeFavs: Int = -1
data class DrawerData(val tags: List<Tag>?, val sources: List<Sources>?)
override fun onStart() {
super.onStart()
customTabActivityHelper.bindCustomTabsService(this)
@ -147,10 +146,15 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
settings = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
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()
appColors = AppColors(this@HomeActivity)
appColors = AppColors(this@HomeActivity)
handleBottomBar()
handleDrawer()
@ -163,21 +167,36 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
swipeRefreshLayout.setColorSchemeResources(
R.color.refresh_progress_1,
R.color.refresh_progress_2,
R.color.refresh_progress_3)
R.color.refresh_progress_3
)
swipeRefreshLayout.setOnRefreshListener {
handleDrawerItems()
getElementsAccordingToTab()
}
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 =
if (elementsShown != UNREAD_SHOWN) 0 else super.getSwipeDirs(recyclerView, viewHolder)
override fun onMove(recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder): Boolean = false
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean = false
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, swipeDir: Int) {
try {
@ -193,28 +212,37 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
if (items.size > 0) {
badgeNew--
reloadBadgeContent()
}
else
} else {
tabNewBadge.hide()
}
val manager = recyclerView.layoutManager
val lastVisibleItem: Int = when (manager) {
is StaggeredGridLayoutManager -> manager.findLastCompletelyVisibleItemPositions(null).last()
is StaggeredGridLayoutManager -> manager.findLastCompletelyVisibleItemPositions(
null
).last()
is GridLayoutManager -> manager.findLastCompletelyVisibleItemPosition()
else -> 0
}
if (lastVisibleItem === items.size && items.size <= maxItemNumber() && maxItemNumber() >= itemsNumber) {
getElementsAccordingToTab(appendResults = true, offsetOverride = lastVisibleItem)
if (lastVisibleItem === items.size &&
items.size <= maxItemNumber() &&
maxItemNumber() >= itemsNumber
) {
getElementsAccordingToTab(
appendResults = true,
offsetOverride = lastVisibleItem
)
}
} catch (e: IndexOutOfBoundsException) {
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)
}
}
}
@ -264,7 +292,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
bottomBar.setMode(BottomNavigationBar.MODE_SHIFTING)
bottomBar.setBackgroundStyle(BottomNavigationBar.BACKGROUND_STYLE_STATIC)
}
override fun onResume() {
@ -290,7 +317,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
customTabActivityHelper.unbindCustomTabsService(this)
}
private fun handleSharedPrefs() {
debugReadingItems = sharedPref.getBoolean("read_debug", false)
clickBehavior = sharedPref.getBoolean("tab_on_tap", false)
@ -327,7 +353,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
bottomBar.hide()
}
if (displayAccountHeader)
if (displayAccountHeader) {
accountHeader {
background = R.drawable.bg
profile(settings.getString("url", "")) {
@ -335,13 +361,17 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
}
selectionListEnabledForSingleProfile = false
}
}
footer {
primaryItem(R.string.drawer_report_bug) {
icon = R.drawable.ic_bug_report
iconTintingEnabled = true
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)
.guestToken(BuildConfig.GITHUB_TOKEN)
.guestEmailRequired(true)
@ -370,21 +400,20 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
}
}
}
}
private fun handleDrawerItems() {
fun handleDrawerData(maybeDrawerData: DrawerData?, loadedFromCache: Boolean = false) {
fun handleTags(maybeTags: List<Tag>?) {
if (maybeTags == null) {
if (loadedFromCache)
if (loadedFromCache) {
drawer.addItem(
SecondaryDrawerItem()
.withName(getString(R.string.drawer_error_loading_tags))
.withSelectable(false))
}
else {
.withSelectable(false)
)
}
} else {
for (tag in maybeTags) {
val gd = GradientDrawable()
gd.setColor(Color.parseColor(tag.color))
@ -409,19 +438,19 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
)
}
}
}
fun handleSources(maybeSources: List<Sources>?) {
if (maybeSources == null) {
if (loadedFromCache)
if (loadedFromCache) {
drawer.addItem(
SecondaryDrawerItem()
.withName(getString(R.string.drawer_error_loading_sources))
.withSelectable(false))
}
else
for (tag in maybeSources)
.withSelectable(false)
)
}
} else {
for (tag in maybeSources) {
drawer.addItem(
CustomUrlPrimaryDrawerItem()
.withName(tag.title)
@ -433,7 +462,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
false
}
)
}
}
}
drawer.removeAllItems()
@ -456,7 +486,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
SecondaryDrawerItem()
.withName(getString(R.string.drawer_item_tags))
.withIdentifier(DRAWER_ID_TAGS)
.withSelectable(false))
.withSelectable(false)
)
handleTags(maybeDrawerData.tags)
drawer.addItem(DividerDrawerItem())
drawer.addItem(
@ -481,41 +512,46 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
.withOnDrawerItemClickListener { _, _, _ ->
LibsBuilder()
.withActivityStyle(
if (appColors.isDarkTheme)
if (appColors.isDarkTheme) {
Libs.ActivityStyle.LIGHT_DARK_TOOLBAR
else
} else {
Libs.ActivityStyle.DARK
}
)
.withAboutIconShown(true)
.withAboutVersionShown(true)
.start(this@HomeActivity)
false
})
}
)
if (!loadedFromCache)
Reservoir.putAsync("drawerData", maybeDrawerData, object : ReservoirPutCallback {
override fun onSuccess() {}
if (!loadedFromCache) {
Reservoir.putAsync(
"drawerData", maybeDrawerData, object : ReservoirPutCallback {
override fun onSuccess() {
}
override fun onFailure(p0: Exception?) {
}
})
}
} else {
if (!loadedFromCache) {
drawer.addItem(
PrimaryDrawerItem()
.withName(getString(R.string.no_tags_loaded))
.withIdentifier(DRAWER_ID_TAGS)
.withSelectable(false))
.withSelectable(false)
)
drawer.addItem(
PrimaryDrawerItem()
.withName(getString(R.string.no_sources_loaded))
.withIdentifier(DRAWER_ID_SOURCES)
.withSelectable(false))
.withSelectable(false)
)
}
}
}
fun drawerApiCalls(maybeDrawerData: DrawerData?) {
@ -523,23 +559,28 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
var sources: List<Sources>?
fun sourcesApiCall() {
api.sources.enqueue(object: Callback<List<Sources>> {
override fun onResponse(call: Call<List<Sources>>?, response: Response<List<Sources>>) {
api.sources.enqueue(object : Callback<List<Sources>> {
override fun onResponse(
call: Call<List<Sources>>?,
response: Response<List<Sources>>
) {
sources = response.body()
val apiDrawerData = DrawerData(tags, sources)
if ((maybeDrawerData != null && maybeDrawerData != apiDrawerData) || maybeDrawerData == null)
if ((maybeDrawerData != null && maybeDrawerData != apiDrawerData) || maybeDrawerData == null) {
handleDrawerData(apiDrawerData)
}
}
override fun onFailure(call: Call<List<Sources>>?, t: Throwable?) {
}
})
}
api.tags.enqueue(object: Callback<List<Tag>> {
override fun onResponse(call: Call<List<Tag>>, response: Response<List<Tag>>) {
api.tags.enqueue(object : Callback<List<Tag>> {
override fun onResponse(
call: Call<List<Tag>>,
response: Response<List<Tag>>
) {
tags = response.body()
sourcesApiCall()
}
@ -547,14 +588,18 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
override fun onFailure(call: Call<List<Tag>>?, t: Throwable?) {
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
Reservoir.getAsync("drawerData", resultType, object: ReservoirGetCallback<DrawerData> {
Reservoir.getAsync(
"drawerData", resultType, object : ReservoirGetCallback<DrawerData> {
override fun onSuccess(maybeDrawerData: DrawerData?) {
handleDrawerData(maybeDrawerData, loadedFromCache = true)
drawerApiCalls(maybeDrawerData)
@ -563,14 +608,16 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
override fun onFailure(p0: Exception?) {
drawerApiCalls(null)
}
})
}
private fun reloadLayoutManager() {
val mLayoutManager: RecyclerView.LayoutManager
if (shouldBeCardView) {
mLayoutManager = StaggeredGridLayoutManager(calculateNoOfColumns(), StaggeredGridLayoutManager.VERTICAL)
mLayoutManager = StaggeredGridLayoutManager(
calculateNoOfColumns(),
StaggeredGridLayoutManager.VERTICAL
)
mLayoutManager.gapStrategy = StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
} else {
mLayoutManager = GridLayoutManager(this, calculateNoOfColumns())
@ -616,18 +663,19 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
else -> Unit
}
}
})
}
private fun handleInfiniteScroll() {
if (recyclerViewScrollListener == null)
if (recyclerViewScrollListener == null) {
recyclerViewScrollListener = object : RecyclerView.OnScrollListener() {
override fun onScrolled(localRecycler: RecyclerView?, dx: Int, dy: Int) {
if (localRecycler != null && dy > 0) {
val manager = recyclerView.layoutManager
val lastVisibleItem: Int = when (manager) {
is StaggeredGridLayoutManager -> manager.findLastCompletelyVisibleItemPositions(null).last()
is StaggeredGridLayoutManager -> manager.findLastCompletelyVisibleItemPositions(
null
).last()
is GridLayoutManager -> manager.findLastCompletelyVisibleItemPosition()
else -> 0
}
@ -638,6 +686,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
}
}
}
}
recyclerView.clearOnScrollListeners()
recyclerView.addOnScrollListener(recyclerViewScrollListener)
@ -652,8 +701,11 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
recyclerView.visibility = View.VISIBLE
}
private fun getElementsAccordingToTab(appendResults: Boolean = false, offsetOverride: Int? = null) {
offset = if (appendResults && offsetOverride === null) {
private fun getElementsAccordingToTab(
appendResults: Boolean = false,
offsetOverride: Int? = null
) {
offset = if (appendResults && offsetOverride === null) {
(offset + itemsNumber)
} else {
offsetOverride ?: 0
@ -668,66 +720,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>>) {
val didUpdate = (response.body() != items)
if (response.body() != null) {
if (response.body() != items) {
if (appendResults)
if (appendResults) {
items.addAll(response.body() as ArrayList<Item>)
else
} else {
items = response.body() as ArrayList<Item>
}
}
} else {
if (!appendResults)
if (!appendResults) {
items = ArrayList()
}
}
if (didUpdate)
if (didUpdate) {
handleListResult(appendResults)
}
mayBeEmpty()
swipeRefreshLayout.isRefreshing = false
}
if (!swipeRefreshLayout.isRefreshing)
if (!swipeRefreshLayout.isRefreshing) {
swipeRefreshLayout.post { swipeRefreshLayout.isRefreshing = true }
}
call(maybeTagFilter?.tag, maybeSourceFilter?.id?.toLong(), maybeSearchFilter)
.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)
}
override fun onFailure(call: Call<List<Item>>, t: Throwable) {
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) {
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) {
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) {
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) {
if (appendResults) {
val oldManager = recyclerView.layoutManager
firstVisible = if ((oldManager is StaggeredGridLayoutManager)) {
oldManager.findFirstCompletelyVisibleItemPositions(null).last()
firstVisible = when (oldManager) {
is StaggeredGridLayoutManager ->
oldManager.findFirstCompletelyVisibleItemPositions(null).last()
is GridLayoutManager ->
oldManager.findFirstCompletelyVisibleItemPosition()
else -> 0
}
else
(oldManager as GridLayoutManager)?.findFirstCompletelyVisibleItemPosition()
}
reloadLayoutManager()
@ -745,7 +838,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
fullHeightCards,
appColors,
debugReadingItems,
userIdentifier)
userIdentifier
)
} else {
mAdapter =
ItemListAdapter(
@ -757,10 +851,15 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
internalBrowser,
articleViewer,
debugReadingItems,
userIdentifier)
userIdentifier
)
recyclerView.addItemDecoration(DividerItemDecoration(this@HomeActivity,
DividerItemDecoration.VERTICAL))
recyclerView.addItemDecoration(
DividerItemDecoration(
this@HomeActivity,
DividerItemDecoration.VERTICAL
)
)
}
recyclerView.adapter = mAdapter
mAdapter.notifyDataSetChanged()
@ -785,7 +884,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
}
}
override fun onFailure(call: Call<Stats>, t: Throwable) {}
override fun onFailure(call: Call<Stats>, t: Throwable) {
}
})
} else {
reloadBadgeContent(succeeded = false)
@ -794,10 +894,11 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
private fun reloadBadgeContent(succeeded: Boolean = true) {
if (succeeded) {
if (displayUnreadCount)
if (displayUnreadCount) {
tabNewBadge
.setText(badgeNew.toString())
.maybeShow()
}
if (displayAllCount) {
tabArchiveBadge
.setText(badgeAll.toString())
@ -851,7 +952,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
}
else -> super.onActivityResult(req, result, data)
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
@ -869,14 +969,23 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
when (item.itemId) {
R.id.refresh -> {
api.update().enqueue(object : Callback<String> {
override fun onResponse(call: Call<String>, response: Response<String>) {
Toast.makeText(this@HomeActivity,
R.string.refresh_success_response, Toast.LENGTH_LONG)
override fun onResponse(
call: Call<String>,
response: Response<String>
) {
Toast.makeText(
this@HomeActivity,
R.string.refresh_success_response, Toast.LENGTH_LONG
)
.show()
}
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()
@ -888,25 +997,45 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
val ids = items.map { it.id }
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) {
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()
} 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
}
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
}
})
items = ArrayList()
if (items.isEmpty())
Toast.makeText(this@HomeActivity, R.string.nothing_here, Toast.LENGTH_SHORT).show()
if (items.isEmpty()) {
Toast.makeText(
this@HomeActivity,
R.string.nothing_here,
Toast.LENGTH_SHORT
).show()
}
handleListResult()
}
return true
@ -939,10 +1068,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
}
private fun maxItemNumber(): Int =
when (elementsShown) {
UNREAD_SHOWN -> badgeNew
READ_SHOWN -> badgeAll
FAV_SHOWN -> badgeFavs
else -> badgeNew // if !elementsShown then unread are fetched.
}
when (elementsShown) {
UNREAD_SHOWN -> badgeNew
READ_SHOWN -> badgeAll
FAV_SHOWN -> badgeFavs
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.view.View
class IntroActivity : MaterialIntroActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
@ -18,33 +17,44 @@ class IntroActivity : MaterialIntroActivity() {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
addSlide(SlideFragmentBuilder()
.backgroundColor(R.color.colorPrimary)
.buttonsColor(R.color.colorAccent)
.image(R.drawable.web_hi_res_512)
.title(getString(R.string.intro_hello_title))
.description(getString(R.string.intro_hello_message))
.build())
addSlide(
SlideFragmentBuilder()
.backgroundColor(R.color.colorPrimary)
.buttonsColor(R.color.colorAccent)
.image(R.drawable.web_hi_res_512)
.title(getString(R.string.intro_hello_title))
.description(getString(R.string.intro_hello_message))
.build()
)
addSlide(SlideFragmentBuilder()
.backgroundColor(R.color.colorAccent)
.buttonsColor(R.color.colorPrimary)
.image(R.drawable.ic_info_outline_white_48px)
.title(getString(R.string.intro_needs_selfoss_title))
.description(getString(R.string.intro_needs_selfoss_message))
.build(),
MessageButtonBehaviour(View.OnClickListener {
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://selfoss.aditu.de"))
startActivity(browserIntent)
}, getString(R.string.intro_needs_selfoss_link)))
addSlide(
SlideFragmentBuilder()
.backgroundColor(R.color.colorAccent)
.buttonsColor(R.color.colorPrimary)
.image(R.drawable.ic_info_outline_white_48px)
.title(getString(R.string.intro_needs_selfoss_title))
.description(getString(R.string.intro_needs_selfoss_message))
.build(),
MessageButtonBehaviour(
View.OnClickListener {
val browserIntent = Intent(
Intent.ACTION_VIEW,
Uri.parse("https://selfoss.aditu.de")
)
startActivity(browserIntent)
}, getString(R.string.intro_needs_selfoss_link)
)
)
addSlide(SlideFragmentBuilder()
.backgroundColor(R.color.colorPrimaryDark)
.buttonsColor(R.color.colorAccentDark)
.image(R.drawable.ic_thumb_up_white_48px)
.title(getString(R.string.intro_all_set_title))
.description(getString(R.string.intro_all_set_message))
.build())
addSlide(
SlideFragmentBuilder()
.backgroundColor(R.color.colorPrimaryDark)
.buttonsColor(R.color.colorAccentDark)
.image(R.drawable.ic_thumb_up_white_48px)
.title(getString(R.string.intro_all_set_title))
.description(getString(R.string.intro_all_set_message))
.build()
)
}
override fun onFinish() {

View File

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

View File

@ -5,15 +5,16 @@ import android.os.Bundle
import android.preference.PreferenceManager
import android.support.v7.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
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)
startActivity(i)
} else {
@ -22,6 +23,5 @@ class MainActivity : AppCompatActivity() {
}
finish()
}
}

View File

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

View File

@ -23,12 +23,11 @@ import com.bumptech.glide.request.RequestOptions
import com.crashlytics.android.Crashlytics
import com.ftinc.scoop.Scoop
import com.github.rubensousa.floatingtoolbar.FloatingToolbar
import kotlinx.android.synthetic.main.activity_reader.*
import org.sufficientlysecure.htmltextview.HtmlHttpImageGetter
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import kotlinx.android.synthetic.main.activity_reader.*
class ReaderActivity : AppCompatActivity() {
private lateinit var mCustomTabActivityHelper: CustomTabActivityHelper
@ -40,7 +39,6 @@ class ReaderActivity : AppCompatActivity() {
private lateinit var contentTitle: String
private lateinit var fab: FloatingActionButton
override fun onStop() {
super.onStop()
mCustomTabActivityHelper.unbindCustomTabsService(this)
@ -81,7 +79,8 @@ class ReaderActivity : AppCompatActivity() {
customTabsIntent,
false,
false,
this@ReaderActivity)
this@ReaderActivity
)
else -> Unit
}
}
@ -111,23 +110,34 @@ class ReaderActivity : AppCompatActivity() {
}
}
nestedScrollView.setOnScrollChangeListener(NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
if (scrollY > oldScrollY) {
fab.hide()
} else {
if (mFloatingToolbar.isShowing) mFloatingToolbar.hide() else fab.show()
}
})
nestedScrollView.setOnScrollChangeListener(
NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
if (scrollY > oldScrollY) {
fab.hide()
} else {
if (mFloatingToolbar.isShowing) mFloatingToolbar.hide() else fab.show()
}
}
)
content.movementMethod = LinkMovementMethod.getInstance()
}
private fun getContentFromMercury(customTabsIntent: CustomTabsIntent, prefs: SharedPreferences) {
private fun getContentFromMercury(
customTabsIntent: CustomTabsIntent,
prefs: SharedPreferences
) {
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> {
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()) {
source.text = response.body()!!.domain
titleView.text = response.body()!!.title
@ -152,14 +162,23 @@ class ReaderActivity : AppCompatActivity() {
nestedScrollView.scrollTo(0, 0)
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 {
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.Sources
import com.ftinc.scoop.Scoop
import kotlinx.android.synthetic.main.activity_sources.*
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import kotlinx.android.synthetic.main.activity_sources.*
class SourcesActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Scoop.getInstance().apply(this)
@ -39,25 +38,43 @@ class SourcesActivity : AppCompatActivity() {
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()
recyclerView.setHasFixedSize(true)
recyclerView.layoutManager = mLayoutManager
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()) {
items = response.body() as ArrayList<Sources>
}
val mAdapter = SourcesListAdapter(this@SourcesActivity, items, api)
recyclerView.adapter = mAdapter
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) {
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.like.LikeButton
import com.like.OnLikeListener
import kotlinx.android.synthetic.main.card_item.view.*
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import kotlinx.android.synthetic.main.card_item.view.*
class ItemCardAdapter(private val app: Activity,
private val items: ArrayList<Item>,
private val api: SelfossApi,
private val helper: CustomTabActivityHelper,
private val internalBrowser: Boolean,
private val articleViewer: Boolean,
private val fullHeightCards: Boolean,
private val appColors: AppColors,
val debugReadingItems: Boolean,
val userIdentifier: String) : RecyclerView.Adapter<ItemCardAdapter.ViewHolder>() {
class ItemCardAdapter(
private val app: Activity,
private val items: ArrayList<Item>,
private val api: SelfossApi,
private val helper: CustomTabActivityHelper,
private val internalBrowser: Boolean,
private val articleViewer: Boolean,
private val fullHeightCards: Boolean,
private val appColors: AppColors,
val debugReadingItems: Boolean,
val userIdentifier: String
) : RecyclerView.Adapter<ItemCardAdapter.ViewHolder>() {
private val c: Context = app.baseContext
private val generator: ColorGenerator = ColorGenerator.MATERIAL
@ -94,13 +96,21 @@ class ItemCardAdapter(private val app: Activity,
private fun doUnmark(i: Item, position: Int) {
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) {
items.add(position, i)
notifyItemInserted(position)
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) {
items.remove(i)
@ -124,7 +134,10 @@ class ItemCardAdapter(private val app: Activity,
notifyItemRemoved(position)
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) {
val message =
"message: ${response.message()} " +
@ -150,12 +163,15 @@ class ItemCardAdapter(private val app: Activity,
Crashlytics.logException(t)
Toast.makeText(c, t.message, Toast.LENGTH_LONG).show()
}
Toast.makeText(app, app.getString(R.string.cant_mark_read), Toast.LENGTH_SHORT).show()
Toast.makeText(
app,
app.getString(R.string.cant_mark_read),
Toast.LENGTH_SHORT
).show()
items.add(i)
notifyItemInserted(position)
}
})
}
inner class ViewHolder(val mView: CardView) : RecyclerView.ViewHolder(mView) {
@ -176,11 +192,22 @@ class ItemCardAdapter(private val app: Activity,
override fun liked(likeButton: LikeButton) {
val (id) = items[adapterPosition]
api.starrItem(id).enqueue(object : Callback<SuccessResponse> {
override fun onResponse(call: Call<SuccessResponse>, response: Response<SuccessResponse>) {}
override fun 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
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) {
val (id) = items[adapterPosition]
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
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)
mView.setOnClickListener {
c.openItemUrl(items[adapterPosition].getLinkDecoded(),
c.openItemUrl(
items[adapterPosition].getLinkDecoded(),
items[adapterPosition].content,
items[adapterPosition].getThumbnail(c),
items[adapterPosition].title,
@ -220,7 +259,8 @@ class ItemCardAdapter(private val app: Activity,
customTabsIntent,
internalBrowser,
articleViewer,
app)
app
)
}
}
}

View File

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

View File

@ -7,8 +7,6 @@ import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import apps.amine.bou.readerforselfoss.R
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
@ -18,20 +16,25 @@ import apps.amine.bou.readerforselfoss.utils.glide.circularBitmapDrawable
import apps.amine.bou.readerforselfoss.utils.toTextDrawableString
import com.amulyakhare.textdrawable.TextDrawable
import com.amulyakhare.textdrawable.util.ColorGenerator
import kotlinx.android.synthetic.main.source_list_item.view.*
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import kotlinx.android.synthetic.main.source_list_item.view.*
class SourcesListAdapter(private val app: Activity,
private val items: ArrayList<Sources>,
private val api: SelfossApi) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>() {
class SourcesListAdapter(
private val app: Activity,
private val items: ArrayList<Sources>,
private val api: SelfossApi
) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>() {
private val c: Context = app.baseContext
private val generator: ColorGenerator = ColorGenerator.MATERIAL
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)
}
@ -69,23 +72,32 @@ class SourcesListAdapter(private val app: Activity,
deleteBtn.setOnClickListener {
val (id) = items[adapterPosition]
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) {
items.removeAt(adapterPosition)
notifyItemRemoved(adapterPosition)
notifyItemRangeChanged(adapterPosition, itemCount)
} 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) {
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.converter.gson.GsonConverterFactory
class MercuryApi(private val key: String, shouldLog: Boolean) {
private val service: MercuryService
init {
val interceptor = HttpLoggingInterceptor()
interceptor.level = if (shouldLog)
interceptor.level = if (shouldLog) {
HttpLoggingInterceptor.Level.BODY
else
} else {
HttpLoggingInterceptor.Level.NONE
}
val client = OkHttpClient.Builder().addInterceptor(interceptor).build()
val gson = GsonBuilder()
.setLenient()
.create()
val retrofit =
Retrofit
.Builder()
.baseUrl("https://mercury.postlight.com")
.client(client)
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
Retrofit
.Builder()
.baseUrl("https://mercury.postlight.com")
.client(client)
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
service = retrofit.create(MercuryService::class.java)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,4 +4,4 @@ import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
import retrofit2.Response
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 com.google.firebase.remoteconfig.FirebaseRemoteConfig
fun String?.isEmptyOrNullOrNullString(): Boolean =
this == null || this == "null" || this.isEmpty()
this == null || this == "null" || this.isEmpty()
fun Context.checkApkVersion(settings: SharedPreferences,
editor: SharedPreferences.Editor,
mFirebaseRemoteConfig: FirebaseRemoteConfig) = {
fun Context.checkApkVersion(
settings: SharedPreferences,
editor: SharedPreferences.Editor,
mFirebaseRemoteConfig: FirebaseRemoteConfig
) = {
fun isThereAnUpdate() {
val APK_LINK = "github_apk"
@ -24,31 +25,35 @@ fun Context.checkApkVersion(settings: SharedPreferences,
val alertDialog = AlertDialog.Builder(this).create()
alertDialog.setTitle(getString(R.string.new_apk_available_title))
alertDialog.setMessage(getString(R.string.new_apk_available_message))
alertDialog.setButton(AlertDialog.BUTTON_POSITIVE, getString(R.string.new_apk_available_get)) { _, _ ->
alertDialog.setButton(
AlertDialog.BUTTON_POSITIVE,
getString(R.string.new_apk_available_get)
) { _, _ ->
editor.putString(APK_LINK, apkLink)
editor.apply()
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(apkLink))
startActivity(browserIntent)
}
alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, getString(R.string.new_apk_available_no),
{ dialog, _ ->
editor.putString(APK_LINK, apkLink)
editor.apply()
dialog.dismiss()
})
alertDialog.setButton(
AlertDialog.BUTTON_NEUTRAL, getString(R.string.new_apk_available_no),
{ dialog, _ ->
editor.putString(APK_LINK, apkLink)
editor.apply()
dialog.dismiss()
}
)
alertDialog.show()
}
}
mFirebaseRemoteConfig.fetch(43200)
.addOnCompleteListener { task ->
if (task.isSuccessful) {
mFirebaseRemoteConfig.activateFetched()
}
.addOnCompleteListener { task ->
if (task.isSuccessful) {
mFirebaseRemoteConfig.activateFetched()
}
isThereAnUpdate()
}
isThereAnUpdate()
}
}
fun String.longHash(): Long {
@ -62,11 +67,12 @@ fun String.longHash(): Long {
return h
}
fun String.toStringUriWithHttp() =
if (!this.startsWith("https://") && !this.startsWith("http://"))
"http://" + this
else
this
fun String.toStringUriWithHttp(): String =
if (!this.startsWith("https://") && !this.startsWith("http://")) {
"http://" + this
} else {
this
}
fun Context.shareLink(itemUrl: String) {
val sendIntent = Intent()
@ -74,5 +80,10 @@ fun Context.shareLink(itemUrl: String) {
sendIntent.action = Intent.ACTION_SEND
sendIntent.putExtra(Intent.EXTRA_TEXT, itemUrl.toStringUriWithHttp())
sendIntent.type = "text/plain"
startActivity(Intent.createChooser(sendIntent, getString(R.string.share)).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
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 apps.amine.bou.readerforselfoss.LoginActivity
class Config(c: Context) {
val settings: SharedPreferences = c.getSharedPreferences(settingsName, Context.MODE_PRIVATE)
@ -26,21 +25,23 @@ class Config(c: Context) {
val httpUserPassword: String
get() = settings.getString("httpPassword", "")
companion object {
val settingsName = "paramsselfoss"
fun logoutAndRedirect(c: Context,
callingActivity: Activity,
editor: SharedPreferences.Editor,
baseUrlFail: Boolean = false): Boolean {
fun logoutAndRedirect(
c: Context,
callingActivity: Activity,
editor: SharedPreferences.Editor,
baseUrlFail: Boolean = false
): Boolean {
editor.remove("url")
editor.remove("login")
editor.remove("password")
editor.apply()
val intent = Intent(c, LoginActivity::class.java)
if (baseUrlFail)
if (baseUrlFail) {
intent.putExtra("baseUrlFail", baseUrlFail)
}
c.startActivity(intent)
callingActivity.finish()
return true

View File

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

View File

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

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.utils.customtabs.CustomTabActivityHelper
import okhttp3.HttpUrl
import xyz.klinker.android.drag_dismiss.DragDismissIntentBuilder
fun Context.buildCustomTabsIntent(): CustomTabsIntent {
val actionIntent = Intent(Intent.ACTION_SEND)
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()
@ -32,32 +34,40 @@ fun Context.buildCustomTabsIntent(): CustomTabsIntent {
intentBuilder.setShowTitle(true)
intentBuilder.setStartAnimations(this,
intentBuilder.setStartAnimations(
this,
R.anim.slide_in_right,
R.anim.slide_out_left)
intentBuilder.setExitAnimations(this,
R.anim.slide_out_left
)
intentBuilder.setExitAnimations(
this,
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)
intentBuilder.setCloseButtonIcon(closeicon)
val shareLabel = this.getString(R.string.label_share)
val icon = BitmapFactory.decodeResource(resources,
R.drawable.ic_share_white_24dp)
val icon = BitmapFactory.decodeResource(
resources,
R.drawable.ic_share_white_24dp
)
intentBuilder.setActionButton(icon, shareLabel, createPendingShareIntent)
return intentBuilder.build()
}
fun Context.openItemUrlInternally(linkDecoded: String,
content: String,
image: String,
title: String,
source: String,
customTabsIntent: CustomTabsIntent,
articleViewer: Boolean,
app: Activity) {
fun Context.openItemUrlInternally(
linkDecoded: String,
content: String,
image: String,
title: String,
source: String,
customTabsIntent: CustomTabsIntent,
articleViewer: Boolean,
app: Activity
) {
if (articleViewer) {
val intent = Intent(this, ReaderActivity::class.java)
@ -76,7 +86,10 @@ fun Context.openItemUrlInternally(linkDecoded: String,
app.startActivity(intent)
} else {
try {
CustomTabActivityHelper.openCustomTab(app, customTabsIntent, Uri.parse(linkDecoded)
CustomTabActivityHelper.openCustomTab(
app,
customTabsIntent,
Uri.parse(linkDecoded)
) { _, uri ->
val intent = Intent(Intent.ACTION_VIEW, uri)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
@ -88,23 +101,38 @@ fun Context.openItemUrlInternally(linkDecoded: String,
}
}
fun Context.openItemUrl(linkDecoded: String,
content: String,
image: String,
title: String,
source: String,
customTabsIntent: CustomTabsIntent,
internalBrowser: Boolean,
articleViewer: Boolean,
app: Activity) {
fun Context.openItemUrl(
linkDecoded: String,
content: String,
image: String,
title: String,
source: String,
customTabsIntent: CustomTabsIntent,
internalBrowser: Boolean,
articleViewer: Boolean,
app: Activity
) {
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 {
if (!internalBrowser) {
openInBrowser(linkDecoded, app)
} 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.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,
directTargetChild: View, target: View, nestedScrollAxes: Int): Boolean {
override fun onStartNestedScroll(
coordinatorLayout: CoordinatorLayout,
child: FloatingActionButton,
directTargetChild: View,
target: View,
nestedScrollAxes: Int
): Boolean {
return true
}
override fun onNestedScroll(coordinatorLayout: CoordinatorLayout,
child: FloatingActionButton,
target: View, dxConsumed: Int, dyConsumed: Int,
dxUnconsumed: Int, dyUnconsumed: Int) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed)
override fun onNestedScroll(
coordinatorLayout: CoordinatorLayout,
child: FloatingActionButton,
target: View,
dxConsumed: Int,
dyConsumed: Int,
dxUnconsumed: Int,
dyUnconsumed: Int
) {
super.onNestedScroll(
coordinatorLayout,
child,
target,
dxConsumed,
dyConsumed,
dxUnconsumed,
dyUnconsumed
)
if (dyConsumed > 0 && child.visibility == View.VISIBLE) {
child.hide(object : FloatingActionButton.OnVisibilityChangedListener() {
override fun onHidden(fab: FloatingActionButton?) {
@ -25,7 +46,6 @@ class ScrollAwareFABBehavior(context: Context, attrs: AttributeSet) : Coordinato
fab!!.visibility = View.INVISIBLE
}
})
} else if (dyConsumed < 0 && child.visibility != View.VISIBLE) {
child.show()
}

View File

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

View File

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

View File

@ -15,8 +15,6 @@ import com.mikepenz.materialdrawer.util.DrawerImageLoader
import com.mikepenz.materialdrawer.util.DrawerUIUtils
import com.mikepenz.materialize.util.UIUtils
abstract class CustomUrlBasePrimaryDrawerItem<T, VH : RecyclerView.ViewHolder> : BaseDrawerItem<T, VH>() {
fun withIcon(url: String): T {
this.icon = ImageHolder(url)
@ -77,7 +75,10 @@ abstract class CustomUrlBasePrimaryDrawerItem<T, VH : RecyclerView.ViewHolder> :
val selectedIconColor = getSelectedIconColor(ctx)
//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
StringHolder.applyTo(this.getName(), viewHolder.name)
//set the text for the description or hide
@ -86,8 +87,11 @@ abstract class CustomUrlBasePrimaryDrawerItem<T, VH : RecyclerView.ViewHolder> :
//set the colors for textViews
viewHolder.name.setTextColor(getTextColorStateList(color, selectedTextColor))
//set the description text color
ColorHolder.applyToOr(descriptionTextColor,
viewHolder.description, getTextColorStateList(color, selectedTextColor))
ColorHolder.applyToOr(
descriptionTextColor,
viewHolder.description,
getTextColorStateList(color, selectedTextColor)
)
//define the typeface for our textViews
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.model.interfaces.ColorfulBadgeable
class CustomUrlPrimaryDrawerItem : CustomUrlBasePrimaryDrawerItem<CustomUrlPrimaryDrawerItem, CustomUrlPrimaryDrawerItem.ViewHolder>(), ColorfulBadgeable<CustomUrlPrimaryDrawerItem> {
protected var mBadge: StringHolder = StringHolder("")
protected var mBadgeStyle = BadgeStyle()
@ -64,7 +63,10 @@ class CustomUrlPrimaryDrawerItem : CustomUrlBasePrimaryDrawerItem<CustomUrlPrima
val badgeVisible = StringHolder.applyToOrHide(mBadge, viewHolder.badge)
//style the badge if it is visible
if (badgeVisible) {
mBadgeStyle.style(viewHolder.badge, getTextColorStateList(getColor(ctx), getSelectedTextColor(ctx)))
mBadgeStyle.style(
viewHolder.badge,
getTextColorStateList(getColor(ctx), getSelectedTextColor(ctx))
)
viewHolder.badgeContainer.visibility = View.VISIBLE
} else {
viewHolder.badgeContainer.visibility = View.GONE
@ -86,6 +88,5 @@ class CustomUrlPrimaryDrawerItem : CustomUrlBasePrimaryDrawerItem<CustomUrlPrima
class ViewHolder(view: View) : CustomBaseViewHolder(view) {
val badgeContainer: View = view.findViewById(R.id.material_drawer_badge_container)
val badge: TextView = view.findViewById(R.id.material_drawer_badge)
}
}

View File

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

View File

@ -10,10 +10,10 @@ import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.module.GlideModule
import java.io.InputStream
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?) {
@ -22,11 +22,12 @@ class SelfSignedGlideModule : GlideModule {
if (pref.getBoolean("isSelfSignedCert", false)) {
val client = getUnsafeHttpClient().build()
registry?.append(GlideUrl::class.java, InputStream::class.java,
com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader.Factory(client))
registry?.append(
GlideUrl::class.java,
InputStream::class.java,
com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader.Factory(client)
)
}
}
}
}