feature/api_timeout_and_settings_cleaning #45
@ -1,3 +0,0 @@
|
||||
package bou.amine.apps.readerforselfossv2.android
|
||||
|
||||
// TODO: test source adding
|
@ -1,102 +0,0 @@
|
||||
package bou.amine.apps.readerforselfossv2.android
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.test.InstrumentationRegistry
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.action.ViewActions.closeSoftKeyboard
|
||||
import androidx.test.espresso.action.ViewActions.pressKey
|
||||
import androidx.test.espresso.action.ViewActions.typeText
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.intent.Intents
|
||||
import androidx.test.espresso.intent.Intents.intended
|
||||
import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import androidx.test.rule.ActivityTestRule
|
||||
import androidx.test.runner.AndroidJUnit4
|
||||
import android.view.KeyEvent
|
||||
import androidx.test.espresso.matcher.RootMatchers.isDialog
|
||||
import bou.amine.apps.readerforselfossv2.android.HomeActivity
|
||||
import bou.amine.apps.readerforselfossv2.android.LoginActivity
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class HomeActivityEspressoTest {
|
||||
lateinit var context: Context
|
||||
|
||||
@Rule @JvmField
|
||||
val rule = ActivityTestRule(HomeActivity::class.java, true, false)
|
||||
|
||||
@Before
|
||||
fun clearData() {
|
||||
context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
|
||||
val editor =
|
||||
context
|
||||
.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
|
||||
.edit()
|
||||
editor.clear()
|
||||
|
||||
editor.putString("url", BuildConfig.LOGIN_URL)
|
||||
editor.putString("login", BuildConfig.LOGIN_USERNAME)
|
||||
editor.putString("password", BuildConfig.LOGIN_PASSWORD)
|
||||
|
||||
editor.commit()
|
||||
|
||||
Intents.init()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun menuItems() {
|
||||
|
||||
rule.launchActivity(Intent())
|
||||
|
||||
onView(
|
||||
withMenu(
|
||||
id = R.id.action_search,
|
||||
titleId = R.string.menu_home_search
|
||||
)
|
||||
).perform(click())
|
||||
|
||||
onView(withId(R.id.search_bar)).check(matches(isDisplayed()))
|
||||
|
||||
onView(withId(R.id.search_src_text)).perform(
|
||||
typeText("android"),
|
||||
pressKey(KeyEvent.KEYCODE_SEARCH),
|
||||
closeSoftKeyboard()
|
||||
)
|
||||
|
||||
onView(withContentDescription(R.string.abc_toolbar_collapse_description)).perform(click())
|
||||
|
||||
openActionBarOverflowOrOptionsMenu(context)
|
||||
|
||||
onView(withMenu(id = R.id.refresh, titleId = R.string.menu_home_refresh))
|
||||
.perform(click())
|
||||
|
||||
onView(withText(android.R.string.ok))
|
||||
.inRoot(isDialog()).check(matches(isDisplayed())).perform(click())
|
||||
|
||||
openActionBarOverflowOrOptionsMenu(context)
|
||||
|
||||
onView(withText(R.string.action_disconnect)).perform(click())
|
||||
|
||||
intended(hasComponent(LoginActivity::class.java.name))
|
||||
}
|
||||
|
||||
// TODO: test articles opening and actions for cards and lists
|
||||
|
||||
@After
|
||||
fun releaseIntents() {
|
||||
Intents.release()
|
||||
}
|
||||
}
|
@ -1,180 +0,0 @@
|
||||
package bou.amine.apps.readerforselfossv2.android
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.test.InstrumentationRegistry
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.action.ViewActions.closeSoftKeyboard
|
||||
import androidx.test.espresso.action.ViewActions.pressBack
|
||||
import androidx.test.espresso.action.ViewActions.typeText
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.intent.Intents
|
||||
import androidx.test.espresso.intent.Intents.intended
|
||||
import androidx.test.espresso.intent.Intents.times
|
||||
import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
|
||||
import androidx.test.espresso.matcher.ViewMatchers
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isRoot
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import androidx.test.rule.ActivityTestRule
|
||||
import androidx.test.runner.AndroidJUnit4
|
||||
import bou.amine.apps.readerforselfossv2.android.HomeActivity
|
||||
import bou.amine.apps.readerforselfossv2.android.LoginActivity
|
||||
import bou.amine.apps.readerforselfossv2.android.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
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class LoginActivityEspressoTest {
|
||||
|
||||
@Rule @JvmField
|
||||
val rule = ActivityTestRule(LoginActivity::class.java, true, false)
|
||||
|
||||
private lateinit var context: Context
|
||||
private lateinit var url: String
|
||||
private lateinit var username: String
|
||||
private lateinit var password: String
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
val editor =
|
||||
context
|
||||
.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
|
||||
.edit()
|
||||
editor.clear()
|
||||
editor.commit()
|
||||
|
||||
|
||||
url = BuildConfig.LOGIN_URL
|
||||
username = BuildConfig.LOGIN_USERNAME
|
||||
password = BuildConfig.LOGIN_PASSWORD
|
||||
|
||||
Intents.init()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun menuItems() {
|
||||
|
||||
rule.launchActivity(Intent())
|
||||
|
||||
openActionBarOverflowOrOptionsMenu(context)
|
||||
|
||||
onView(withText(R.string.action_about)).perform(click())
|
||||
|
||||
intended(hasComponent(LibsActivity::class.java.name), times(1))
|
||||
|
||||
onView(isRoot()).perform(pressBack())
|
||||
|
||||
intended(hasComponent(LoginActivity::class.java.name))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun wrongLoginUrl() {
|
||||
rule.launchActivity(Intent())
|
||||
|
||||
onView(withId(R.id.loginProgress))
|
||||
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)))
|
||||
|
||||
onView(withId(R.id.urlView)).perform(click()).perform(typeText("WRONGURL"))
|
||||
|
||||
onView(withId(R.id.signInButton)).perform(click())
|
||||
|
||||
onView(withId(R.id.urlView)).check(matches(isHintOrErrorEnabled()))
|
||||
}
|
||||
|
||||
// TODO: Add tests for multiple false urls with dialog
|
||||
|
||||
@Test
|
||||
fun emptyAuthData() {
|
||||
|
||||
rule.launchActivity(Intent())
|
||||
|
||||
onView(withId(R.id.urlView)).perform(click()).perform(typeText(url), closeSoftKeyboard())
|
||||
|
||||
onView(withId(R.id.withLogin)).perform(click())
|
||||
|
||||
onView(withId(R.id.signInButton)).perform(click())
|
||||
|
||||
onView(withId(R.id.loginView)).check(matches(isHintOrErrorEnabled()))
|
||||
onView(withId(R.id.passwordView)).check(matches(isHintOrErrorEnabled()))
|
||||
|
||||
onView(withId(R.id.loginView)).perform(click()).perform(
|
||||
typeText(username),
|
||||
closeSoftKeyboard()
|
||||
)
|
||||
|
||||
onView(withId(R.id.passwordView)).check(matches(isHintOrErrorEnabled()))
|
||||
|
||||
onView(withId(R.id.signInButton)).perform(click())
|
||||
|
||||
onView(withId(R.id.passwordView)).check(
|
||||
matches(
|
||||
isHintOrErrorEnabled()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun wrongAuthData() {
|
||||
|
||||
rule.launchActivity(Intent())
|
||||
|
||||
onView(withId(R.id.urlView)).perform(click()).perform(typeText(url), closeSoftKeyboard())
|
||||
|
||||
onView(withId(R.id.withLogin)).perform(click())
|
||||
|
||||
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.signInButton)).perform(click())
|
||||
|
||||
onView(withId(R.id.urlView)).check(matches(isHintOrErrorEnabled()))
|
||||
onView(withId(R.id.loginView)).check(matches(isHintOrErrorEnabled()))
|
||||
onView(withId(R.id.passwordView)).check(matches(isHintOrErrorEnabled()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun workingAuth() {
|
||||
|
||||
rule.launchActivity(Intent())
|
||||
|
||||
onView(withId(R.id.urlView)).perform(click()).perform(typeText(url), closeSoftKeyboard())
|
||||
|
||||
onView(withId(R.id.withLogin)).perform(click())
|
||||
|
||||
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.signInButton)).perform(click())
|
||||
|
||||
Thread.sleep(2000)
|
||||
intended(hasComponent(HomeActivity::class.java.name))
|
||||
}
|
||||
|
||||
@After
|
||||
fun releaseIntents() {
|
||||
Intents.release()
|
||||
}
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
package bou.amine.apps.readerforselfossv2.android
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import androidx.test.InstrumentationRegistry.getInstrumentation
|
||||
import androidx.test.espresso.intent.Intents
|
||||
import androidx.test.espresso.intent.Intents.intended
|
||||
import androidx.test.espresso.intent.Intents.times
|
||||
import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
|
||||
import androidx.test.rule.ActivityTestRule
|
||||
import androidx.test.runner.AndroidJUnit4
|
||||
import bou.amine.apps.readerforselfossv2.android.HomeActivity
|
||||
import bou.amine.apps.readerforselfossv2.android.LoginActivity
|
||||
import bou.amine.apps.readerforselfossv2.android.MainActivity
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
||||
import org.junit.After
|
||||
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class MainActivityEspressoTest {
|
||||
|
||||
lateinit var intent: Intent
|
||||
lateinit var preferencesEditor: SharedPreferences.Editor
|
||||
private lateinit var url: String
|
||||
private lateinit var username: String
|
||||
private lateinit var password: String
|
||||
|
||||
@Rule @JvmField
|
||||
val rule = ActivityTestRule(MainActivity::class.java, true, false)
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
intent = Intent()
|
||||
val context = getInstrumentation().targetContext
|
||||
|
||||
// create a SharedPreferences editor
|
||||
preferencesEditor = context.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE).edit()
|
||||
|
||||
url = BuildConfig.LOGIN_URL
|
||||
username = BuildConfig.LOGIN_USERNAME
|
||||
password = BuildConfig.LOGIN_PASSWORD
|
||||
|
||||
Intents.init()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkFirstOpenLaunchesIntro() {
|
||||
preferencesEditor.putString("url", "")
|
||||
preferencesEditor.putString("password", "")
|
||||
preferencesEditor.putString("login", "")
|
||||
preferencesEditor.commit()
|
||||
|
||||
rule.launchActivity(intent)
|
||||
|
||||
intended(hasComponent(LoginActivity::class.java.name))
|
||||
intended(hasComponent(HomeActivity::class.java.name), times(0))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkNotFirstOpenLaunchesLogin() {
|
||||
preferencesEditor.putString("url", url)
|
||||
preferencesEditor.putString("password", password)
|
||||
preferencesEditor.putString("login", username)
|
||||
preferencesEditor.commit()
|
||||
|
||||
rule.launchActivity(intent)
|
||||
|
||||
intended(hasComponent(MainActivity::class.java.name))
|
||||
intended(hasComponent(HomeActivity::class.java.name))
|
||||
}
|
||||
|
||||
@After
|
||||
fun releaseIntents() {
|
||||
Intents.release()
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
package bou.amine.apps.readerforselfossv2.android
|
||||
|
||||
import androidx.test.espresso.matcher.ViewMatchers
|
||||
import android.view.View
|
||||
import android.widget.EditText
|
||||
import org.hamcrest.Description
|
||||
import org.hamcrest.Matcher
|
||||
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 EditText) {
|
||||
return false
|
||||
}
|
||||
|
||||
return item.error.isNotEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
fun withMenu(id: Int, titleId: Int): Matcher<View> =
|
||||
Matchers.anyOf(
|
||||
ViewMatchers.withId(id),
|
||||
ViewMatchers.withText(titleId)
|
||||
)
|
@ -9,10 +9,10 @@ import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityAddSourceBinding
|
||||
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
|
||||
import bou.amine.apps.readerforselfossv2.android.themes.Toppings
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlValid
|
||||
import bou.amine.apps.readerforselfossv2.model.NetworkUnavailableException
|
||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
|
||||
import com.ftinc.scoop.Scoop
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@ -31,6 +31,7 @@ class AddSourceActivity : AppCompatActivity(), DIAware {
|
||||
|
||||
override val di by closestDI()
|
||||
private val repository : Repository by instance()
|
||||
private val apiDetailsService : ApiDetailsService by instance()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
appColors = AppColors(this@AddSourceActivity)
|
||||
@ -81,9 +82,9 @@ class AddSourceActivity : AppCompatActivity(), DIAware {
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
val config = Config()
|
||||
|
||||
if (config.baseUrl.isEmpty() || !config.baseUrl.isBaseUrlValid(this@AddSourceActivity)) {
|
||||
val baseUrl = apiDetailsService.getBaseUrl()
|
||||
if (baseUrl.isEmpty() || !baseUrl.isBaseUrlValid(this@AddSourceActivity)) {
|
||||
mustLoginToAddSource()
|
||||
} else {
|
||||
handleSpoutsSpinner(binding.spoutsSpinner, binding.progress, binding.formContainer)
|
||||
|
@ -30,13 +30,12 @@ import bou.amine.apps.readerforselfossv2.android.databinding.ActivityHomeBinding
|
||||
import bou.amine.apps.readerforselfossv2.android.settings.SettingsActivity
|
||||
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
|
||||
import bou.amine.apps.readerforselfossv2.android.themes.Toppings
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.bottombar.maybeShow
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.bottombar.removeBadge
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper
|
||||
import bou.amine.apps.readerforselfossv2.dao.ACTION
|
||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
|
||||
import bou.amine.apps.readerforselfossv2.utils.*
|
||||
import com.ashokvarma.bottomnavigation.BottomNavigationBar
|
||||
import com.ashokvarma.bottomnavigation.BottomNavigationItem
|
||||
@ -84,10 +83,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
private var displayUnreadCount = false
|
||||
private var displayAllCount = false
|
||||
private var fullHeightCards: Boolean = false
|
||||
private var itemsNumber: Int = 200
|
||||
private var elementsShown: ItemType = ItemType.UNREAD
|
||||
private var displayAccountHeader: Boolean = false
|
||||
private var infiniteScroll: Boolean = false
|
||||
private var lastFetchDone: Boolean = false
|
||||
private var updateSources: Boolean = true
|
||||
private var markOnScroll: Boolean = false
|
||||
@ -114,8 +110,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
|
||||
private lateinit var tagsBadge: Map<Long, Int>
|
||||
|
||||
private lateinit var config: Config
|
||||
|
||||
override val di by closestDI()
|
||||
private val repository : Repository by instance()
|
||||
|
||||
@ -128,7 +122,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
appColors = AppColors(this@HomeActivity)
|
||||
config = Config()
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityHomeBinding.inflate(layoutInflater)
|
||||
@ -308,7 +301,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
|
||||
reloadLayoutManager()
|
||||
|
||||
if (!infiniteScroll) {
|
||||
if (!settings.getBoolean("infinite_loading", false)) {
|
||||
binding.recyclerView.setHasFixedSize(true)
|
||||
} else {
|
||||
handleInfiniteScroll()
|
||||
@ -331,15 +324,14 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
}
|
||||
|
||||
private fun handleSettings() {
|
||||
// TODO : clean this
|
||||
internalBrowser = settings.getBoolean("prefer_internal_browser", true)
|
||||
articleViewer = settings.getBoolean("prefer_article_viewer", true)
|
||||
shouldBeCardView = settings.getBoolean("card_view_active", false)
|
||||
displayUnreadCount = settings.getBoolean("display_unread_count", true)
|
||||
displayAllCount = settings.getBoolean("display_other_count", false)
|
||||
fullHeightCards = settings.getBoolean("full_height_cards", false)
|
||||
itemsNumber = settings.getString("prefer_api_items_number", "200").toInt()
|
||||
displayAccountHeader = settings.getBoolean("account_header_displaying", false)
|
||||
infiniteScroll = settings.getBoolean("infinite_loading", false)
|
||||
|
||||
updateSources = settings.getBoolean("update_sources", true)
|
||||
markOnScroll = settings.getBoolean("mark_on_scroll", false)
|
||||
hiddenTags = if (settings.getString("hidden_tags", "").isNotEmpty()) {
|
||||
@ -409,7 +401,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
|
||||
binding.drawerContainer.addDrawerListener(drawerListener)
|
||||
|
||||
displayAccountHeader =
|
||||
val displayAccountHeader =
|
||||
settings.getBoolean("account_header_displaying", false)
|
||||
|
||||
binding.mainDrawer.addStickyFooterItem(
|
||||
@ -418,7 +410,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
iconRes = R.drawable.ic_bug_report_black_24dp
|
||||
isIconTinted = true
|
||||
onDrawerItemClickListener = { _, _, _ ->
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(Config.trackerUrl))
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(ApiDetailsService.trackerUrl))
|
||||
startActivity(browserIntent)
|
||||
false
|
||||
}
|
||||
@ -451,6 +443,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
}
|
||||
|
||||
// TODO: refactor this.
|
||||
// TODO: use updateSources
|
||||
private fun handleDrawerItems() {
|
||||
tagsBadge = emptyMap()
|
||||
fun handleDrawerData(maybeDrawerData: DrawerData?, loadedFromCache: Boolean = false) {
|
||||
@ -875,11 +868,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
this,
|
||||
items,
|
||||
customTabActivityHelper,
|
||||
internalBrowser,
|
||||
articleViewer,
|
||||
fullHeightCards,
|
||||
internalBrowser, // TODO remove and use from apidetailsservice
|
||||
articleViewer, // TODO remove and use from apidetailsservice
|
||||
fullHeightCards, // TODO remove and use from apidetailsservice
|
||||
appColors,
|
||||
config
|
||||
) {
|
||||
updateItems(it)
|
||||
}
|
||||
@ -889,10 +881,9 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
this,
|
||||
items,
|
||||
customTabActivityHelper,
|
||||
internalBrowser,
|
||||
articleViewer,
|
||||
internalBrowser, // TODO remove and use from apidetailsservice
|
||||
articleViewer, // TODO remove and use from apidetailsservice
|
||||
appColors,
|
||||
config
|
||||
) {
|
||||
updateItems(it)
|
||||
}
|
||||
@ -1047,7 +1038,12 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
return true
|
||||
}
|
||||
R.id.action_disconnect -> {
|
||||
return Config.logoutAndRedirect(this, this@HomeActivity)
|
||||
val settings = Settings()
|
||||
settings.clear()
|
||||
val intent = Intent(this, LoginActivity::class.java)
|
||||
this.startActivity(intent)
|
||||
this@HomeActivity.finish()
|
||||
return true
|
||||
}
|
||||
else -> return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
@ -14,12 +14,11 @@ import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import androidx.multidex.MultiDexApplication
|
||||
import androidx.preference.PreferenceManager
|
||||
import bou.amine.apps.readerforselfossv2.DI.networkModule
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.loadMaybeBasicAuth
|
||||
import bou.amine.apps.readerforselfossv2.android.viewmodel.AppViewModel
|
||||
import bou.amine.apps.readerforselfossv2.dao.DriverFactory
|
||||
import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB
|
||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import com.ftinc.scoop.Scoop
|
||||
@ -49,13 +48,11 @@ class MyApp : MultiDexApplication(), DIAware {
|
||||
private val viewModel: AppViewModel by instance()
|
||||
private val connectivityStatus: ConnectivityStatus by instance()
|
||||
private val driverFactory: DriverFactory by instance()
|
||||
private lateinit var config: Config
|
||||
private lateinit var settings : Settings
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
Napier.base(DebugAntilog())
|
||||
config = Config()
|
||||
settings = Settings()
|
||||
|
||||
initDrawerImageLoader()
|
||||
@ -93,11 +90,11 @@ class MyApp : MultiDexApplication(), DIAware {
|
||||
|
||||
val name = getString(R.string.notification_channel_sync)
|
||||
val importance = NotificationManager.IMPORTANCE_LOW
|
||||
val mChannel = NotificationChannel(Config.syncChannelId, name, importance)
|
||||
val mChannel = NotificationChannel(ApiDetailsService.syncChannelId, name, importance)
|
||||
|
||||
val newItemsChannelname = getString(R.string.new_items_channel_sync)
|
||||
val newItemsChannelimportance = NotificationManager.IMPORTANCE_DEFAULT
|
||||
val newItemsChannelmChannel = NotificationChannel(Config.newItemsChannelId, newItemsChannelname, newItemsChannelimportance)
|
||||
val newItemsChannelmChannel = NotificationChannel(ApiDetailsService.newItemsChannelId, newItemsChannelname, newItemsChannelimportance)
|
||||
|
||||
notificationManager.createNotificationChannel(mChannel)
|
||||
notificationManager.createNotificationChannel(newItemsChannelmChannel)
|
||||
@ -108,7 +105,7 @@ class MyApp : MultiDexApplication(), DIAware {
|
||||
DrawerImageLoader.init(object : AbstractDrawerImageLoader() {
|
||||
override fun set(imageView: ImageView, uri: Uri, placeholder: Drawable, tag: String?) {
|
||||
Glide.with(imageView.context)
|
||||
.loadMaybeBasicAuth(config, uri.toString())
|
||||
.load(uri.toString())
|
||||
.apply(RequestOptions.fitCenterTransform().placeholder(placeholder))
|
||||
.into(imageView)
|
||||
}
|
||||
|
@ -38,7 +38,6 @@ class ItemCardAdapter(
|
||||
private val articleViewer: Boolean,
|
||||
private val fullHeightCards: Boolean,
|
||||
override val appColors: AppColors,
|
||||
override val config: Config,
|
||||
override val updateItems: (ArrayList<SelfossModel.Item>) -> Unit
|
||||
) : ItemsAdapter<ItemCardAdapter.ViewHolder>() {
|
||||
private val c: Context = app.baseContext
|
||||
@ -78,7 +77,7 @@ class ItemCardAdapter(
|
||||
binding.itemImage.setImageDrawable(null)
|
||||
} else {
|
||||
binding.itemImage.visibility = View.VISIBLE
|
||||
c.bitmapCenterCrop(config, itm.getThumbnail(repository.baseUrl), binding.itemImage)
|
||||
c.bitmapCenterCrop(itm.getThumbnail(repository.baseUrl), binding.itemImage)
|
||||
}
|
||||
|
||||
if (itm.getIcon(repository.baseUrl).isEmpty()) {
|
||||
@ -91,7 +90,7 @@ class ItemCardAdapter(
|
||||
.build(itm.title.getHtmlDecoded().toTextDrawableString(), color)
|
||||
binding.sourceImage.setImageDrawable(drawable)
|
||||
} else {
|
||||
c.circularBitmapDrawable(config, itm.getIcon(repository.baseUrl), binding.sourceImage)
|
||||
c.circularBitmapDrawable(itm.getIcon(repository.baseUrl), binding.sourceImage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,6 @@ class ItemListAdapter(
|
||||
private val internalBrowser: Boolean,
|
||||
private val articleViewer: Boolean,
|
||||
override val appColors: AppColors,
|
||||
override val config: Config,
|
||||
override val updateItems: (ArrayList<SelfossModel.Item>) -> Unit
|
||||
) : ItemsAdapter<ItemListAdapter.ViewHolder>() {
|
||||
private val generator: ColorGenerator = ColorGenerator.MATERIAL
|
||||
@ -69,10 +68,10 @@ class ItemListAdapter(
|
||||
|
||||
binding.itemImage.setImageDrawable(drawable)
|
||||
} else {
|
||||
c.circularBitmapDrawable(config, itm.getIcon(repository.baseUrl), binding.itemImage)
|
||||
c.circularBitmapDrawable(itm.getIcon(repository.baseUrl), binding.itemImage)
|
||||
}
|
||||
} else {
|
||||
c.bitmapCenterCrop(config, itm.getThumbnail(repository.baseUrl), binding.itemImage)
|
||||
c.bitmapCenterCrop(itm.getThumbnail(repository.baseUrl), binding.itemImage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import bou.amine.apps.readerforselfossv2.android.R
|
||||
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.utils.ItemType
|
||||
@ -21,7 +20,6 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
|
||||
abstract val repository: Repository
|
||||
abstract val app: Activity
|
||||
abstract val appColors: AppColors
|
||||
abstract val config: Config
|
||||
abstract val updateItems: (ArrayList<SelfossModel.Item>) -> Unit
|
||||
|
||||
fun updateAllItems(items: ArrayList<SelfossModel.Item>) {
|
||||
|
@ -11,7 +11,6 @@ import androidx.recyclerview.widget.RecyclerView
|
||||
import bou.amine.apps.readerforselfossv2.android.R
|
||||
import bou.amine.apps.readerforselfossv2.android.databinding.SourceListItemBinding
|
||||
import bou.amine.apps.readerforselfossv2.android.model.toTextDrawableString
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularBitmapDrawable
|
||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
@ -33,7 +32,6 @@ class SourcesListAdapter(
|
||||
) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>(), DIAware {
|
||||
private val c: Context = app.baseContext
|
||||
private val generator: ColorGenerator = ColorGenerator.MATERIAL
|
||||
private lateinit var config: Config
|
||||
private lateinit var binding: SourceListItemBinding
|
||||
|
||||
override val di: DI by closestDI(app)
|
||||
@ -46,7 +44,6 @@ class SourcesListAdapter(
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val itm = items[position]
|
||||
config = Config()
|
||||
|
||||
if (itm.getIcon(repository.baseUrl).isEmpty()) {
|
||||
val color = generator.getColor(itm.title.getHtmlDecoded())
|
||||
@ -58,7 +55,7 @@ class SourcesListAdapter(
|
||||
.build(itm.title.getHtmlDecoded().toTextDrawableString(), color)
|
||||
binding.itemImage.setImageDrawable(drawable)
|
||||
} else {
|
||||
c.circularBitmapDrawable(config, itm.getIcon(repository.baseUrl), binding.itemImage)
|
||||
c.circularBitmapDrawable(itm.getIcon(repository.baseUrl), binding.itemImage)
|
||||
}
|
||||
|
||||
binding.sourceTitle.text = itm.title.getHtmlDecoded()
|
||||
|
@ -14,11 +14,10 @@ import bou.amine.apps.readerforselfossv2.android.MainActivity
|
||||
import bou.amine.apps.readerforselfossv2.android.MyApp
|
||||
import bou.amine.apps.readerforselfossv2.android.R
|
||||
import bou.amine.apps.readerforselfossv2.android.model.preloadImages
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAccessible
|
||||
import bou.amine.apps.readerforselfossv2.dao.ACTION
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
|
||||
import com.russhwolf.settings.Settings
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@ -27,7 +26,6 @@ import org.kodein.di.DIAware
|
||||
import org.kodein.di.instance
|
||||
import java.util.*
|
||||
import kotlin.concurrent.schedule
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
class LoadingWorker(val context: Context, params: WorkerParameters) : Worker(context, params), DIAware {
|
||||
|
||||
@ -44,12 +42,12 @@ override fun doWork(): Result {
|
||||
applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
val notification =
|
||||
NotificationCompat.Builder(applicationContext, Config.syncChannelId)
|
||||
NotificationCompat.Builder(applicationContext, ApiDetailsService.syncChannelId)
|
||||
.setContentTitle(context.getString(R.string.loading_notification_title))
|
||||
.setContentText(context.getString(R.string.loading_notification_text))
|
||||
.setOngoing(true)
|
||||
.setPriority(PRIORITY_LOW)
|
||||
.setChannelId(Config.syncChannelId)
|
||||
.setChannelId(ApiDetailsService.syncChannelId)
|
||||
.setSmallIcon(R.drawable.ic_stat_cloud_download_black_24dp)
|
||||
|
||||
notificationManager.notify(1, notification.build())
|
||||
@ -89,7 +87,7 @@ override fun doWork(): Result {
|
||||
val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, pflags)
|
||||
|
||||
val newItemsNotification =
|
||||
NotificationCompat.Builder(applicationContext, Config.newItemsChannelId)
|
||||
NotificationCompat.Builder(applicationContext, ApiDetailsService.newItemsChannelId)
|
||||
.setContentTitle(context.getString(R.string.new_items_notification_title))
|
||||
.setContentText(
|
||||
context.getString(
|
||||
@ -98,7 +96,7 @@ override fun doWork(): Result {
|
||||
)
|
||||
)
|
||||
.setPriority(PRIORITY_DEFAULT)
|
||||
.setChannelId(Config.newItemsChannelId)
|
||||
.setChannelId(ApiDetailsService.newItemsChannelId)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setAutoCancel(true)
|
||||
.setSmallIcon(R.drawable.ic_tab_fiber_new_black_24dp)
|
||||
|
@ -29,7 +29,6 @@ import bou.amine.apps.readerforselfossv2.android.themes.AppColors
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.*
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.getBitmapInputStream
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.loadMaybeBasicAuth
|
||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
|
||||
@ -70,7 +69,6 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
private lateinit var fab: FloatingActionButton
|
||||
private lateinit var appColors: AppColors
|
||||
private lateinit var textAlignment: String
|
||||
private lateinit var config: Config
|
||||
private var _binding: FragmentArticleBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
@ -93,7 +91,6 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
appColors = AppColors(requireActivity())
|
||||
config = Config()
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
@ -213,7 +210,7 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
Glide
|
||||
.with(requireContext())
|
||||
.asBitmap()
|
||||
.loadMaybeBasicAuth(config, contentImage)
|
||||
.load(contentImage)
|
||||
.apply(RequestOptions.fitCenterTransform())
|
||||
.into(binding.imageView)
|
||||
} else {
|
||||
@ -307,8 +304,7 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
Glide
|
||||
.with(requireContext())
|
||||
.asBitmap()
|
||||
.loadMaybeBasicAuth(
|
||||
config,
|
||||
.load(
|
||||
response.body()!!.lead_image_url.orEmpty()
|
||||
)
|
||||
.apply(RequestOptions.fitCenterTransform())
|
||||
|
@ -15,7 +15,7 @@ import androidx.preference.PreferenceFragmentCompat
|
||||
import bou.amine.apps.readerforselfossv2.android.R
|
||||
import bou.amine.apps.readerforselfossv2.android.databinding.ActivitySettingsBinding
|
||||
import bou.amine.apps.readerforselfossv2.android.themes.Toppings
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
||||
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
|
||||
import com.ftinc.scoop.Scoop
|
||||
import com.russhwolf.settings.Settings
|
||||
import java.lang.NumberFormatException
|
||||
@ -196,17 +196,17 @@ class SettingsActivity : AppCompatActivity(),
|
||||
setPreferencesFromResource(R.xml.pref_links, rootKey)
|
||||
|
||||
preferenceManager.findPreference<Preference>("trackerLink")?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
openUrl(Uri.parse(Config.trackerUrl))
|
||||
openUrl(Uri.parse(ApiDetailsService.trackerUrl))
|
||||
true
|
||||
}
|
||||
|
||||
preferenceManager.findPreference<Preference>("sourceLink")?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
openUrl(Uri.parse(Config.sourceUrl))
|
||||
openUrl(Uri.parse(ApiDetailsService.sourceUrl))
|
||||
false
|
||||
}
|
||||
|
||||
preferenceManager.findPreference<Preference>("translation")?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
openUrl(Uri.parse(Config.translationUrl))
|
||||
openUrl(Uri.parse(ApiDetailsService.translationUrl))
|
||||
false
|
||||
}
|
||||
}
|
||||
|
@ -1,62 +0,0 @@
|
||||
package bou.amine.apps.readerforselfossv2.android.utils
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import bou.amine.apps.readerforselfossv2.android.LoginActivity
|
||||
import com.russhwolf.settings.Settings
|
||||
|
||||
class Config {
|
||||
|
||||
val settings = Settings()
|
||||
|
||||
val baseUrl: String
|
||||
get() = settings.getString("url", "")
|
||||
|
||||
val userLogin: String
|
||||
get() = settings.getString("login", "")
|
||||
|
||||
val userPassword: String
|
||||
get() = settings.getString("password", "")
|
||||
|
||||
val httpUserLogin: String
|
||||
get() = settings.getString("httpUserName", "")
|
||||
|
||||
val httpUserPassword: String
|
||||
get() = settings.getString("httpPassword", "")
|
||||
|
||||
companion object {
|
||||
const val settingsName = "paramsselfoss"
|
||||
|
||||
const val feedbackEmail = "aminecmi@pm.me.com"
|
||||
|
||||
const val translationUrl = "https://crwd.in/readerforselfoss"
|
||||
|
||||
const val sourceUrl = "https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform"
|
||||
|
||||
const val trackerUrl = "https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/issues"
|
||||
|
||||
const val syncChannelId = "sync-channel-id"
|
||||
|
||||
const val newItemsChannelId = "new-items-channel-id"
|
||||
|
||||
var apiVersion = 0
|
||||
|
||||
/* Execute logout and clear all settings to default */
|
||||
fun logoutAndRedirect(
|
||||
c: Context,
|
||||
callingActivity: Activity,
|
||||
baseUrlFail: Boolean = false
|
||||
): Boolean {
|
||||
val settings = Settings()
|
||||
settings.clear()
|
||||
val intent = Intent(c, LoginActivity::class.java)
|
||||
if (baseUrlFail) {
|
||||
intent.putExtra("baseUrlFail", baseUrlFail)
|
||||
}
|
||||
c.startActivity(intent)
|
||||
callingActivity.finish()
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
@ -2,33 +2,26 @@ package bou.amine.apps.readerforselfossv2.android.utils.glide
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.util.Base64
|
||||
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory
|
||||
import android.widget.ImageView
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
||||
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.RequestBuilder
|
||||
import com.bumptech.glide.RequestManager
|
||||
import com.bumptech.glide.load.model.GlideUrl
|
||||
import com.bumptech.glide.load.model.LazyHeaders
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import com.bumptech.glide.request.target.BitmapImageViewTarget
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.InputStream
|
||||
|
||||
fun Context.bitmapCenterCrop(config: Config, url: String, iv: ImageView) =
|
||||
fun Context.bitmapCenterCrop(url: String, iv: ImageView) =
|
||||
Glide.with(this)
|
||||
.asBitmap()
|
||||
.loadMaybeBasicAuth(config, url)
|
||||
.load(url)
|
||||
.apply(RequestOptions.centerCropTransform())
|
||||
.into(iv)
|
||||
|
||||
fun Context.circularBitmapDrawable(config: Config, url: String, iv: ImageView) =
|
||||
fun Context.circularBitmapDrawable(url: String, iv: ImageView) =
|
||||
Glide.with(this)
|
||||
.asBitmap()
|
||||
.loadMaybeBasicAuth(config, url)
|
||||
.load(url)
|
||||
.apply(RequestOptions.centerCropTransform())
|
||||
.into(object : BitmapImageViewTarget(iv) {
|
||||
override fun setResource(resource: Bitmap?) {
|
||||
@ -41,26 +34,6 @@ fun Context.circularBitmapDrawable(config: Config, url: String, iv: ImageView) =
|
||||
}
|
||||
})
|
||||
|
||||
fun RequestBuilder<Bitmap>.loadMaybeBasicAuth(config: Config, url: String): RequestBuilder<Bitmap> {
|
||||
val builder: LazyHeaders.Builder = LazyHeaders.Builder()
|
||||
if (config.httpUserLogin.isNotEmpty() || config.httpUserPassword.isNotEmpty()) {
|
||||
val basicAuth = "Basic " + Base64.encodeToString("${config.httpUserLogin}:${config.httpUserPassword}".toByteArray(), Base64.NO_WRAP)
|
||||
builder.addHeader("Authorization", basicAuth)
|
||||
}
|
||||
val glideUrl = GlideUrl(url, builder.build())
|
||||
return this.load(glideUrl)
|
||||
}
|
||||
|
||||
fun RequestManager.loadMaybeBasicAuth(config: Config, url: String): RequestBuilder<Drawable> {
|
||||
val builder: LazyHeaders.Builder = LazyHeaders.Builder()
|
||||
if (config.httpUserLogin.isNotEmpty() || config.httpUserPassword.isNotEmpty()) {
|
||||
val basicAuth = "Basic " + Base64.encodeToString("${config.httpUserLogin}:${config.httpUserPassword}".toByteArray(), Base64.NO_WRAP)
|
||||
builder.addHeader("Authorization", basicAuth)
|
||||
}
|
||||
val glideUrl = GlideUrl(url, builder.build())
|
||||
return this.load(glideUrl)
|
||||
}
|
||||
|
||||
fun getBitmapInputStream(bitmap:Bitmap,compressFormat: Bitmap.CompressFormat): InputStream {
|
||||
val byteArrayOutputStream = ByteArrayOutputStream()
|
||||
bitmap.compress(compressFormat, 80, byteArrayOutputStream)
|
||||
|
@ -1,18 +1,21 @@
|
||||
package bou.amine.apps.readerforselfossv2.utils
|
||||
|
||||
import android.text.format.DateUtils
|
||||
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
|
||||
import java.time.Instant
|
||||
import java.time.LocalDateTime
|
||||
import java.time.OffsetDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
actual class DateUtils actual constructor(private val apiMajorVersion: Int) {
|
||||
actual class DateUtils actual constructor(apiDetailsService: ApiDetailsService) {
|
||||
val ads: ApiDetailsService = apiDetailsService // TODO: why is this needed now ?
|
||||
|
||||
|
||||
actual fun parseDate(dateString: String): Long {
|
||||
|
||||
val FORMATTERV1 = "yyyy-MM-dd HH:mm:ss"
|
||||
|
||||
return if (apiMajorVersion >= 4) {
|
||||
return if (ads.getApiVersion() >= 4) {
|
||||
OffsetDateTime.parse(dateString).toInstant().toEpochMilli()
|
||||
} else {
|
||||
LocalDateTime.parse(dateString, DateTimeFormatter.ofPattern(FORMATTERV1)).toInstant(
|
||||
|
@ -2,13 +2,12 @@ package bou.amine.apps.readerforselfossv2.DI
|
||||
|
||||
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
|
||||
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
|
||||
import bou.amine.apps.readerforselfossv2.service.ApiDetailsServiceImpl
|
||||
import org.kodein.di.DI
|
||||
import org.kodein.di.bind
|
||||
import org.kodein.di.instance
|
||||
import org.kodein.di.singleton
|
||||
|
||||
val networkModule by DI.Module {
|
||||
bind<ApiDetailsService>() with singleton { ApiDetailsServiceImpl() }
|
||||
bind<ApiDetailsService>() with singleton { ApiDetailsService() }
|
||||
bind<SelfossApi>() with singleton { SelfossApi(instance()) }
|
||||
}
|
@ -32,7 +32,6 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
|
||||
var itemsCaching = settings.getBoolean("items_caching", false)
|
||||
var offlineOverride = false
|
||||
|
||||
var apiMajorVersion = 0
|
||||
var badgeUnread = 0
|
||||
set(value) {field = if (value < 0) { 0 } else { value } }
|
||||
var badgeAll = 0
|
||||
@ -44,6 +43,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
|
||||
// TODO: Dispatchers.IO not available in KMM, an alternative solution should be found
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
updateApiVersion()
|
||||
dateUtils = DateUtils(apiDetails)
|
||||
reloadBadges()
|
||||
}
|
||||
}
|
||||
@ -54,7 +54,6 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
|
||||
if (isNetworkAvailable()) {
|
||||
fetchedItems = api.getItems(
|
||||
displayedItems.type,
|
||||
settings.getString("prefer_api_items_number", "200").toInt(),
|
||||
offset = 0,
|
||||
tagFilter?.tag,
|
||||
sourceFilter?.id?.toLong(),
|
||||
@ -84,7 +83,6 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
|
||||
val offset = items.size
|
||||
fetchedItems = api.getItems(
|
||||
displayedItems.type,
|
||||
settings.getString("prefer_api_items_number", "200").toInt(),
|
||||
offset,
|
||||
tagFilter?.tag,
|
||||
sourceFilter?.id?.toLong(),
|
||||
@ -104,12 +102,12 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
|
||||
return if (isNetworkAvailable()) {
|
||||
api.getItems(
|
||||
itemType.type,
|
||||
200,
|
||||
0,
|
||||
tagFilter?.tag,
|
||||
sourceFilter?.id?.toLong(),
|
||||
searchFilter,
|
||||
null
|
||||
null,
|
||||
200
|
||||
)
|
||||
} else {
|
||||
emptyList()
|
||||
@ -309,7 +307,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
|
||||
spout,
|
||||
tags,
|
||||
filter,
|
||||
apiMajorVersion
|
||||
apiDetails.getApiVersion()
|
||||
)?.isSuccess == true
|
||||
}
|
||||
|
||||
@ -366,13 +364,13 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
|
||||
}
|
||||
|
||||
private suspend fun updateApiVersion() {
|
||||
apiMajorVersion = settings.getInt("apiVersionMajor", 0)
|
||||
val apiMajorVersion = apiDetails.getApiVersion()
|
||||
|
||||
if (isNetworkAvailable()) {
|
||||
val fetchedVersion = api.version()
|
||||
if (fetchedVersion != null) {
|
||||
apiMajorVersion = fetchedVersion.getApiMajorVersion()
|
||||
settings.putInt("apiVersionMajor", apiMajorVersion)
|
||||
if (fetchedVersion != null && fetchedVersion.getApiMajorVersion() != apiMajorVersion) {
|
||||
settings.putInt("apiVersionMajor", fetchedVersion.getApiMajorVersion())
|
||||
apiDetails.refreshApiVersion()
|
||||
}
|
||||
}
|
||||
dateUtils = DateUtils(apiMajorVersion)
|
||||
|
@ -4,6 +4,7 @@ import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.call.*
|
||||
import io.ktor.client.plugins.*
|
||||
import io.ktor.client.plugins.cache.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.request.forms.*
|
||||
@ -35,6 +36,9 @@ class SelfossApi(private val apiDetailsService: ApiDetailsService) {
|
||||
}
|
||||
level = LogLevel.ALL
|
||||
}
|
||||
install(HttpTimeout) {
|
||||
requestTimeoutMillis = apiDetailsService.getApiTimeout()
|
||||
}
|
||||
/* TODO: Auth as basic
|
||||
if (apiDetailsService.getUserName().isNotEmpty() && apiDetailsService.getPassword().isNotEmpty()) {
|
||||
|
||||
@ -69,12 +73,12 @@ class SelfossApi(private val apiDetailsService: ApiDetailsService) {
|
||||
|
||||
suspend fun getItems(
|
||||
type: String,
|
||||
items: Int,
|
||||
offset: Int,
|
||||
tag: String?,
|
||||
source: Long?,
|
||||
search: String?,
|
||||
updatedSince: String?
|
||||
updatedSince: String?,
|
||||
items: Int? = null
|
||||
): List<SelfossModel.Item>? =
|
||||
client.get(url("/items")) {
|
||||
parameter("username", apiDetailsService.getUserName())
|
||||
@ -84,7 +88,7 @@ class SelfossApi(private val apiDetailsService: ApiDetailsService) {
|
||||
parameter("source", source)
|
||||
parameter("search", search)
|
||||
parameter("updatedsince", updatedSince)
|
||||
parameter("items", items)
|
||||
parameter("items", items ?: apiDetailsService.getItemsNumber())
|
||||
parameter("offset", offset)
|
||||
}.body()
|
||||
|
||||
|
@ -1,10 +1,97 @@
|
||||
package bou.amine.apps.readerforselfossv2.service
|
||||
|
||||
interface ApiDetailsService {
|
||||
fun logApiCalls(message: String)
|
||||
fun getApiVersion(): Int
|
||||
fun getBaseUrl(): String
|
||||
fun getUserName(): String
|
||||
fun getPassword(): String
|
||||
fun refresh()
|
||||
import com.russhwolf.settings.Settings
|
||||
import io.github.aakira.napier.Napier
|
||||
import io.ktor.client.plugins.*
|
||||
|
||||
class ApiDetailsService {
|
||||
val settings: Settings = Settings()
|
||||
private var _apiVersion: Int = -1
|
||||
private var _baseUrl: String = ""
|
||||
private var _userName: String = ""
|
||||
private var _password: String = ""
|
||||
private var _itemsNumber: Int? = null
|
||||
private var _apiTimeout: Long? = null
|
||||
|
||||
fun logApiCalls(message: String) {
|
||||
Napier.d(message, tag = "LogApiCalls")
|
||||
}
|
||||
|
||||
|
||||
fun getApiVersion(): Int {
|
||||
if (_apiVersion == -1) {
|
||||
refreshApiVersion()
|
||||
return _apiVersion
|
||||
}
|
||||
return _apiVersion
|
||||
}
|
||||
|
||||
fun refreshApiVersion() {
|
||||
_apiVersion = settings.getInt("apiVersionMajor", -1)
|
||||
}
|
||||
|
||||
fun getBaseUrl(): String {
|
||||
if (_baseUrl.isEmpty()) {
|
||||
_baseUrl = settings.getString("url", "")
|
||||
}
|
||||
return _baseUrl
|
||||
}
|
||||
|
||||
fun getUserName(): String {
|
||||
if (_userName.isEmpty()) {
|
||||
_userName = settings.getString("login", "")
|
||||
}
|
||||
return _userName
|
||||
}
|
||||
|
||||
fun getPassword(): String {
|
||||
if (_password.isEmpty()) {
|
||||
_password = settings.getString("password", "")
|
||||
}
|
||||
return _password
|
||||
}
|
||||
|
||||
fun getItemsNumber(): Int {
|
||||
if (_itemsNumber == null) {
|
||||
refreshItemsNumber()
|
||||
}
|
||||
return _itemsNumber!!
|
||||
}
|
||||
|
||||
fun refreshItemsNumber() {
|
||||
_itemsNumber = settings.getString("prefer_api_items_number", "200").toInt()
|
||||
}
|
||||
|
||||
fun getApiTimeout(): Long {
|
||||
if (_apiTimeout == null) {
|
||||
refreshApiTimeout()
|
||||
}
|
||||
return _apiTimeout!!
|
||||
}
|
||||
|
||||
fun refreshApiTimeout() {
|
||||
val settingsTimeout = settings.getLong("api_timeout", HttpTimeout.INFINITE_TIMEOUT_MS)
|
||||
_apiTimeout = if (settingsTimeout > 0) settingsTimeout else HttpTimeout.INFINITE_TIMEOUT_MS
|
||||
}
|
||||
|
||||
fun refresh() {
|
||||
_password = settings.getString("password", "")
|
||||
_userName = settings.getString("login", "")
|
||||
_baseUrl = settings.getString("url", "")
|
||||
refreshApiVersion()
|
||||
refreshItemsNumber()
|
||||
refreshApiTimeout()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val translationUrl = "https://crwd.in/readerforselfoss"
|
||||
|
||||
const val sourceUrl = "https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform"
|
||||
|
||||
const val trackerUrl = "https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/issues"
|
||||
|
||||
const val syncChannelId = "sync-channel-id"
|
||||
|
||||
const val newItemsChannelId = "new-items-channel-id"
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
package bou.amine.apps.readerforselfossv2.service
|
||||
|
||||
import com.russhwolf.settings.Settings
|
||||
import io.github.aakira.napier.Napier
|
||||
|
||||
class ApiDetailsServiceImpl : ApiDetailsService {
|
||||
val settings: Settings = Settings()
|
||||
private var _apiVersion: Int = -1
|
||||
private var _baseUrl: String = ""
|
||||
private var _userName: String = ""
|
||||
private var _password: String = ""
|
||||
override fun logApiCalls(message: String) {
|
||||
Napier.d(message, tag = "LogApiCalls")
|
||||
}
|
||||
|
||||
|
||||
override fun getApiVersion(): Int {
|
||||
if (_apiVersion == -1) {
|
||||
_apiVersion = settings.getInt("apiVersionMajor", -1)
|
||||
return _apiVersion
|
||||
}
|
||||
return _apiVersion
|
||||
}
|
||||
|
||||
override fun getBaseUrl(): String {
|
||||
if (_baseUrl.isEmpty()) {
|
||||
_baseUrl = settings.getString("url", "")
|
||||
}
|
||||
return _baseUrl
|
||||
}
|
||||
|
||||
override fun getUserName(): String {
|
||||
if (_userName.isEmpty()) {
|
||||
_userName = settings.getString("login", "")
|
||||
}
|
||||
return _userName
|
||||
}
|
||||
|
||||
override fun getPassword(): String {
|
||||
if (_password.isEmpty()) {
|
||||
_password = settings.getString("password", "")
|
||||
}
|
||||
return _password
|
||||
}
|
||||
|
||||
override fun refresh() {
|
||||
_password = settings.getString("password", "")
|
||||
_userName = settings.getString("login", "")
|
||||
_baseUrl = settings.getString("url", "")
|
||||
_apiVersion = settings.getInt("apiVersionMajor", -1)
|
||||
}
|
||||
}
|
@ -1,12 +1,13 @@
|
||||
package bou.amine.apps.readerforselfossv2.utils
|
||||
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
|
||||
|
||||
|
||||
fun SelfossModel.Item.parseDate(dateUtils: DateUtils): Long =
|
||||
dateUtils.parseDate(this.datetime)
|
||||
|
||||
expect class DateUtils(apiMajorVersion: Int) {
|
||||
expect class DateUtils(apiDetailsService: ApiDetailsService) {
|
||||
fun parseDate(dateString: String): Long
|
||||
|
||||
fun parseRelativeDate(dateString: String): String
|
||||
|
@ -1,6 +1,8 @@
|
||||
package bou.amine.apps.readerforselfossv2.utils
|
||||
|
||||
actual class DateUtils actual constructor(apiMajorVersion: Int) {
|
||||
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
|
||||
|
||||
actual class DateUtils actual constructor(apiDetailsService: ApiDetailsService) {
|
||||
actual fun parseDate(dateString: String): Long {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
package bou.amine.apps.readerforselfossv2.utils
|
||||
|
||||
actual class DateUtils actual constructor(apiMajorVersion: Int) {
|
||||
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
|
||||
|
||||
actual class DateUtils actual constructor(apiDetailsService: ApiDetailsService) {
|
||||
actual fun parseDate(dateString: String): Long {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user
I think you have to declare it's a val in the class constructor in order to make it a class variable and use it within functions.
I couldn't. I had to do as mentionned here to "fix" this.