WIP. Everything isn't working yet, but api calls are made.
This commit is contained in:
parent
bd4d20e858
commit
6e38e8753c
5
BREAKING.md
Normal file
5
BREAKING.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# Breaking changes
|
||||||
|
|
||||||
|
These breaking changes will **NOT** be handled in this version.
|
||||||
|
|
||||||
|
- Basic (kinda) and Digest auth. This version will use the selfoss credentials via Basic authentication.
|
10
TODO.md
Normal file
10
TODO.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# TODO
|
||||||
|
|
||||||
|
|
||||||
|
- Service injection
|
||||||
|
|
||||||
|
- Basic auth
|
||||||
|
* Self signed certs
|
||||||
|
* Timeout + 408
|
||||||
|
|
||||||
|
- Clean HTTP login
|
@ -14,9 +14,6 @@ import android.widget.ProgressBar
|
|||||||
import android.widget.Spinner
|
import android.widget.Spinner
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import bou.amine.apps.readerforselfossv2.android.api.selfoss.SelfossApi
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.api.selfoss.Spout
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.api.selfoss.SuccessResponse
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
|
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
|
||||||
import bou.amine.apps.readerforselfossv2.android.themes.Toppings
|
import bou.amine.apps.readerforselfossv2.android.themes.Toppings
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
||||||
@ -26,10 +23,19 @@ import retrofit2.Call
|
|||||||
import retrofit2.Callback
|
import retrofit2.Callback
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityAddSourceBinding
|
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityAddSourceBinding
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.service.AndroidApiDetailsService
|
||||||
|
|
||||||
|
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
|
||||||
|
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||||
|
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
||||||
class AddSourceActivity : AppCompatActivity() {
|
class AddSourceActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
private lateinit var apiDetailsService: ApiDetailsService
|
||||||
private var mSpoutsValue: String? = null
|
private var mSpoutsValue: String? = null
|
||||||
private lateinit var api: SelfossApi
|
private lateinit var api: SelfossApi
|
||||||
|
|
||||||
@ -74,11 +80,13 @@ class AddSourceActivity : AppCompatActivity() {
|
|||||||
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
|
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
val settings =
|
val settings =
|
||||||
getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
|
getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
|
||||||
|
apiDetailsService = AndroidApiDetailsService(this@AddSourceActivity)
|
||||||
api = SelfossApi(
|
api = SelfossApi(
|
||||||
this,
|
// this,
|
||||||
this@AddSourceActivity,
|
// this@AddSourceActivity,
|
||||||
settings.getBoolean("isSelfSignedCert", false),
|
// settings.getBoolean("isSelfSignedCert", false),
|
||||||
prefs.getString("api_timeout", "-1")!!.toLong()
|
// prefs.getString("api_timeout", "-1")!!.toLong()
|
||||||
|
apiDetailsService
|
||||||
)
|
)
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
mustLoginToAddSource()
|
mustLoginToAddSource()
|
||||||
@ -124,41 +132,28 @@ 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>>
|
|
||||||
) {
|
|
||||||
if (response.body() != null) {
|
|
||||||
items = response.body()!!
|
|
||||||
|
|
||||||
val itemsStrings = items.map { it.value.name }
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
for ((key, value) in items) {
|
var items = api!!.spouts()
|
||||||
spoutsKV[value.name] = key
|
if (items != null) {
|
||||||
}
|
|
||||||
|
|
||||||
mProgress.visibility = View.GONE
|
val itemsStrings = items.map { it.value.name }
|
||||||
formContainer.visibility = View.VISIBLE
|
for ((key, value) in items) {
|
||||||
|
spoutsKV[value.name] = key
|
||||||
val spinnerArrayAdapter =
|
|
||||||
ArrayAdapter(
|
|
||||||
this@AddSourceActivity,
|
|
||||||
android.R.layout.simple_spinner_item,
|
|
||||||
itemsStrings
|
|
||||||
)
|
|
||||||
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
|
||||||
spoutsSpinner.adapter = spinnerArrayAdapter
|
|
||||||
} else {
|
|
||||||
handleProblemWithSpouts()
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(call: Call<Map<String, Spout>>, t: Throwable) {
|
mProgress.visibility = View.GONE
|
||||||
handleProblemWithSpouts()
|
formContainer.visibility = View.VISIBLE
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleProblemWithSpouts() {
|
val spinnerArrayAdapter =
|
||||||
|
ArrayAdapter(
|
||||||
|
this@AddSourceActivity,
|
||||||
|
android.R.layout.simple_spinner_item,
|
||||||
|
itemsStrings
|
||||||
|
)
|
||||||
|
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||||
|
spoutsSpinner.adapter = spinnerArrayAdapter
|
||||||
|
} else {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
this@AddSourceActivity,
|
this@AddSourceActivity,
|
||||||
R.string.cant_get_spouts,
|
R.string.cant_get_spouts,
|
||||||
@ -166,7 +161,7 @@ class AddSourceActivity : AppCompatActivity() {
|
|||||||
).show()
|
).show()
|
||||||
mProgress.visibility = View.GONE
|
mProgress.visibility = View.GONE
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun maybeGetDetailsFromIntentSharing(
|
private fun maybeGetDetailsFromIntentSharing(
|
||||||
@ -196,70 +191,26 @@ class AddSourceActivity : AppCompatActivity() {
|
|||||||
sourceDetailsUnavailable -> {
|
sourceDetailsUnavailable -> {
|
||||||
Toast.makeText(this, R.string.form_not_complete, Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, R.string.form_not_complete, Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
PreferenceManager.getDefaultSharedPreferences(this).getInt("apiVersionMajor", 0) > 1 -> {
|
|
||||||
val tagList = tags.text.toString().split(",").map { it.trim() }
|
|
||||||
api.createSourceApi2(
|
|
||||||
title,
|
|
||||||
url,
|
|
||||||
mSpoutsValue!!,
|
|
||||||
tagList,
|
|
||||||
""
|
|
||||||
).enqueue(object : Callback<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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
|
|
||||||
Toast.makeText(
|
|
||||||
this@AddSourceActivity,
|
|
||||||
R.string.cant_create_source,
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
else -> {
|
else -> {
|
||||||
api.createSource(
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
title,
|
val response: SelfossModel.SuccessResponse? = api.createSourceForVersion(
|
||||||
url,
|
title,
|
||||||
mSpoutsValue!!,
|
url,
|
||||||
tags.text.toString(),
|
mSpoutsValue!!,
|
||||||
""
|
tags.text.toString(),
|
||||||
).enqueue(object : Callback<SuccessResponse> {
|
"",
|
||||||
override fun onResponse(
|
PreferenceManager.getDefaultSharedPreferences(this@AddSourceActivity).getInt("apiVersionMajor", 0)
|
||||||
call: Call<SuccessResponse>,
|
)
|
||||||
response: Response<SuccessResponse>
|
if (response != null) {
|
||||||
) {
|
finish()
|
||||||
if (response.body() != null && response.body()!!.isSuccess) {
|
} else {
|
||||||
finish()
|
|
||||||
} else {
|
|
||||||
Toast.makeText(
|
|
||||||
this@AddSourceActivity,
|
|
||||||
R.string.cant_create_source,
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
|
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
this@AddSourceActivity,
|
this@AddSourceActivity,
|
||||||
R.string.cant_create_source,
|
R.string.cant_create_source,
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,27 +29,37 @@ import androidx.work.WorkManager
|
|||||||
import bou.amine.apps.readerforselfossv2.android.adapters.ItemCardAdapter
|
import bou.amine.apps.readerforselfossv2.android.adapters.ItemCardAdapter
|
||||||
import bou.amine.apps.readerforselfossv2.android.adapters.ItemListAdapter
|
import bou.amine.apps.readerforselfossv2.android.adapters.ItemListAdapter
|
||||||
import bou.amine.apps.readerforselfossv2.android.adapters.ItemsAdapter
|
import bou.amine.apps.readerforselfossv2.android.adapters.ItemsAdapter
|
||||||
import bou.amine.apps.readerforselfossv2.android.api.selfoss.*
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.background.LoadingWorker
|
import bou.amine.apps.readerforselfossv2.android.background.LoadingWorker
|
||||||
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityHomeBinding
|
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityHomeBinding
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.model.getIcon
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.model.getTitleDecoded
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.persistence.AndroidDeviceDatabase
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.persistence.AndroidDeviceDatabaseService
|
||||||
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
|
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
|
||||||
import bou.amine.apps.readerforselfossv2.android.persistence.entities.ActionEntity
|
import bou.amine.apps.readerforselfossv2.android.persistence.entities.ActionEntity
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.persistence.entities.AndroidItemEntity
|
||||||
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_1_2
|
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_1_2
|
||||||
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_2_3
|
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_2_3
|
||||||
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_3_4
|
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_3_4
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.service.AndroidApiDetailsService
|
||||||
import bou.amine.apps.readerforselfossv2.android.settings.SettingsActivity
|
import bou.amine.apps.readerforselfossv2.android.settings.SettingsActivity
|
||||||
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
|
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
|
||||||
import bou.amine.apps.readerforselfossv2.android.themes.Toppings
|
import bou.amine.apps.readerforselfossv2.android.themes.Toppings
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.SharedItems
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.bottombar.maybeShow
|
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.bottombar.removeBadge
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper
|
import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.longHash
|
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAccessible
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.persistence.toEntity
|
import bou.amine.apps.readerforselfossv2.android.utils.persistence.toEntity
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.persistence.toView
|
import bou.amine.apps.readerforselfossv2.android.utils.persistence.toView
|
||||||
import bou.amine.apps.readerforselfossv2.android.api.selfoss.*
|
|
||||||
|
import bou.amine.apps.readerforselfossv2.utils.DateUtils
|
||||||
|
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
|
||||||
|
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||||
|
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
|
||||||
|
import bou.amine.apps.readerforselfossv2.service.SearchService
|
||||||
|
import bou.amine.apps.readerforselfossv2.service.SelfossService
|
||||||
|
import bou.amine.apps.readerforselfossv2.utils.longHash
|
||||||
import com.ashokvarma.bottomnavigation.BottomNavigationBar
|
import com.ashokvarma.bottomnavigation.BottomNavigationBar
|
||||||
import com.ashokvarma.bottomnavigation.BottomNavigationItem
|
import com.ashokvarma.bottomnavigation.BottomNavigationItem
|
||||||
import com.ashokvarma.bottomnavigation.TextBadgeItem
|
import com.ashokvarma.bottomnavigation.TextBadgeItem
|
||||||
@ -73,14 +83,16 @@ import com.mikepenz.materialdrawer.widget.AccountHeaderView
|
|||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import retrofit2.Call
|
|
||||||
import retrofit2.Callback
|
|
||||||
import retrofit2.Response
|
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
|
|
||||||
class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
|
class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
|
||||||
|
|
||||||
|
private lateinit var dataBase: AndroidDeviceDatabase
|
||||||
|
private lateinit var dbService: AndroidDeviceDatabaseService
|
||||||
|
private lateinit var searchService: SearchService
|
||||||
|
private lateinit var apiDetailsService: ApiDetailsService
|
||||||
|
private lateinit var service: SelfossService<AndroidItemEntity>
|
||||||
private val MENU_PREFERENCES = 12302
|
private val MENU_PREFERENCES = 12302
|
||||||
private val DRAWER_ID_TAGS = 100101L
|
private val DRAWER_ID_TAGS = 100101L
|
||||||
private val DRAWER_ID_HIDDEN_TAGS = 101100L
|
private val DRAWER_ID_HIDDEN_TAGS = 101100L
|
||||||
@ -90,8 +102,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
|
|||||||
private val READ_SHOWN = 2
|
private val READ_SHOWN = 2
|
||||||
private val FAV_SHOWN = 3
|
private val FAV_SHOWN = 3
|
||||||
|
|
||||||
private var items: ArrayList<Item> = ArrayList()
|
private var items: ArrayList<SelfossModel.Item> = ArrayList()
|
||||||
private var allItems: ArrayList<Item> = ArrayList()
|
private var allItems: ArrayList<SelfossModel.Item> = ArrayList()
|
||||||
|
|
||||||
private var internalBrowser = false
|
private var internalBrowser = false
|
||||||
private var articleViewer = false
|
private var articleViewer = false
|
||||||
@ -105,7 +117,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
|
|||||||
private var displayAccountHeader: Boolean = false
|
private var displayAccountHeader: Boolean = false
|
||||||
private var infiniteScroll: Boolean = false
|
private var infiniteScroll: Boolean = false
|
||||||
private var lastFetchDone: Boolean = false
|
private var lastFetchDone: Boolean = false
|
||||||
private var itemsCaching: Boolean = false
|
|
||||||
private var updateSources: Boolean = true
|
private var updateSources: Boolean = true
|
||||||
private var markOnScroll: Boolean = false
|
private var markOnScroll: Boolean = false
|
||||||
private var hiddenTags: List<String> = emptyList()
|
private var hiddenTags: List<String> = emptyList()
|
||||||
@ -140,7 +151,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
|
|||||||
|
|
||||||
private lateinit var config: Config
|
private lateinit var config: Config
|
||||||
|
|
||||||
data class DrawerData(val tags: List<Tag>?, val sources: List<Source>?)
|
data class DrawerData(val tags: List<SelfossModel.Tag>?, val sources: List<SelfossModel.Source>?)
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
super.onStart()
|
super.onStart()
|
||||||
@ -184,12 +195,19 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
|
|||||||
sharedPref = PreferenceManager.getDefaultSharedPreferences(this)
|
sharedPref = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
settings = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
|
settings = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
apiDetailsService = AndroidApiDetailsService(applicationContext)
|
||||||
api = SelfossApi(
|
api = SelfossApi(
|
||||||
this,
|
// this,
|
||||||
this@HomeActivity,
|
// this@HomeActivity,
|
||||||
settings.getBoolean("isSelfSignedCert", false),
|
// settings.getBoolean("isSelfSignedCert", false),
|
||||||
sharedPref.getString("api_timeout", "-1")!!.toLong()
|
// sharedPref.getString("api_timeout", "-1")!!.toLong()
|
||||||
|
apiDetailsService
|
||||||
)
|
)
|
||||||
|
|
||||||
|
dataBase = AndroidDeviceDatabase(applicationContext)
|
||||||
|
searchService = SearchService(DateUtils(apiDetailsService))
|
||||||
|
dbService = AndroidDeviceDatabaseService(dataBase, searchService)
|
||||||
|
service = SelfossService(api, dbService, searchService)
|
||||||
items = ArrayList()
|
items = ArrayList()
|
||||||
allItems = ArrayList()
|
allItems = ArrayList()
|
||||||
|
|
||||||
@ -217,7 +235,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
|
|||||||
lastFetchDone = false
|
lastFetchDone = false
|
||||||
handleDrawerItems()
|
handleDrawerItems()
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
refreshFocusedItems(applicationContext, api, db, itemsNumber)
|
service.refreshFocusedItems(itemsNumber, applicationContext.isNetworkAvailable())
|
||||||
getElementsAccordingToTab()
|
getElementsAccordingToTab()
|
||||||
binding.swipeRefreshLayout.isRefreshing = false
|
binding.swipeRefreshLayout.isRefreshing = false
|
||||||
}
|
}
|
||||||
@ -258,7 +276,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
|
|||||||
|
|
||||||
reloadBadgeContent()
|
reloadBadgeContent()
|
||||||
|
|
||||||
val tagHashes = i.tags.tags.split(",").map { it.longHash() }
|
val tagHashes = i.tags.split(",").map { it.longHash() }
|
||||||
tagsBadge = tagsBadge.map {
|
tagsBadge = tagsBadge.map {
|
||||||
if (tagHashes.contains(it.key)) {
|
if (tagHashes.contains(it.key)) {
|
||||||
(it.key to (it.value - 1))
|
(it.key to (it.value - 1))
|
||||||
@ -334,21 +352,13 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getApiMajorVersion() {
|
private fun getApiMajorVersion() {
|
||||||
api.apiVersion.enqueue(object : Callback<ApiVersion> {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
override fun onFailure(call: Call<ApiVersion>, t: Throwable) {
|
val version = api.version()
|
||||||
Config.apiVersion = apiVersionMajor
|
if (version != null) {
|
||||||
|
apiVersionMajor = version.getApiMajorVersion()
|
||||||
|
sharedPref.edit().putInt("apiVersionMajor", apiVersionMajor).apply()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
override fun onResponse(call: Call<ApiVersion>, response: Response<ApiVersion>) {
|
|
||||||
if(response.body() != null) {
|
|
||||||
val version = response.body() as ApiVersion
|
|
||||||
apiVersionMajor = version.getApiMajorVersion()
|
|
||||||
sharedPref.edit().putInt("apiVersionMajor", apiVersionMajor).apply()
|
|
||||||
|
|
||||||
Config.apiVersion = apiVersionMajor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
@ -382,17 +392,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
|
|||||||
getElementsAccordingToTab()
|
getElementsAccordingToTab()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getAndStoreAllItems() {
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
|
||||||
binding.swipeRefreshLayout.isRefreshing = true
|
|
||||||
getAndStoreAllItems(applicationContext ,api, db)
|
|
||||||
this@HomeActivity.isNetworkAccessible(this@HomeActivity.findViewById(R.id.coordLayout), offlineShortcut)
|
|
||||||
handleListResult()
|
|
||||||
binding.swipeRefreshLayout.isRefreshing = false
|
|
||||||
SharedItems.updateDatabase(db)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStop() {
|
override fun onStop() {
|
||||||
super.onStop()
|
super.onStop()
|
||||||
customTabActivityHelper.unbindCustomTabsService(this)
|
customTabActivityHelper.unbindCustomTabsService(this)
|
||||||
@ -409,8 +408,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
|
|||||||
userIdentifier = sharedPref.getString("unique_id", "")!!
|
userIdentifier = sharedPref.getString("unique_id", "")!!
|
||||||
displayAccountHeader = sharedPref.getBoolean("account_header_displaying", false)
|
displayAccountHeader = sharedPref.getBoolean("account_header_displaying", false)
|
||||||
infiniteScroll = sharedPref.getBoolean("infinite_loading", false)
|
infiniteScroll = sharedPref.getBoolean("infinite_loading", false)
|
||||||
itemsCaching = sharedPref.getBoolean("items_caching", false)
|
searchService.itemsCaching = sharedPref.getBoolean("items_caching", false)
|
||||||
SharedItems.itemsCaching = itemsCaching
|
|
||||||
updateSources = sharedPref.getBoolean("update_sources", true)
|
updateSources = sharedPref.getBoolean("update_sources", true)
|
||||||
markOnScroll = sharedPref.getBoolean("mark_on_scroll", false)
|
markOnScroll = sharedPref.getBoolean("mark_on_scroll", false)
|
||||||
hiddenTags = if (sharedPref.getString("hidden_tags", "")!!.isNotEmpty()) {
|
hiddenTags = if (sharedPref.getString("hidden_tags", "")!!.isNotEmpty()) {
|
||||||
@ -525,7 +523,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
|
|||||||
private fun handleDrawerItems() {
|
private fun handleDrawerItems() {
|
||||||
tagsBadge = emptyMap()
|
tagsBadge = emptyMap()
|
||||||
fun handleDrawerData(maybeDrawerData: DrawerData?, loadedFromCache: Boolean = false) {
|
fun handleDrawerData(maybeDrawerData: DrawerData?, loadedFromCache: Boolean = false) {
|
||||||
fun handleTags(maybeTags: List<Tag>?) {
|
fun handleTags(maybeTags: List<SelfossModel.Tag>?) {
|
||||||
if (maybeTags == null) {
|
if (maybeTags == null) {
|
||||||
if (loadedFromCache) {
|
if (loadedFromCache) {
|
||||||
binding.mainDrawer.itemAdapter.add(
|
binding.mainDrawer.itemAdapter.add(
|
||||||
@ -560,9 +558,9 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
|
|||||||
color = ColorHolder.fromColor(appColors.colorAccent) }
|
color = ColorHolder.fromColor(appColors.colorAccent) }
|
||||||
onDrawerItemClickListener = { _,_,_ ->
|
onDrawerItemClickListener = { _,_,_ ->
|
||||||
allItems = ArrayList()
|
allItems = ArrayList()
|
||||||
SharedItems.tagFilter = it.tag
|
searchService.tagFilter = it.tag
|
||||||
SharedItems.sourceFilter = null
|
searchService.sourceFilter = null
|
||||||
SharedItems.sourceIDFilter = null
|
searchService.sourceIDFilter = null
|
||||||
getElementsAccordingToTab()
|
getElementsAccordingToTab()
|
||||||
fetchOnEmptyList()
|
fetchOnEmptyList()
|
||||||
false
|
false
|
||||||
@ -578,7 +576,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleHiddenTags(maybeTags: List<Tag>?) {
|
fun handleHiddenTags(maybeTags: List<SelfossModel.Tag>?) {
|
||||||
if (maybeTags == null) {
|
if (maybeTags == null) {
|
||||||
if (loadedFromCache) {
|
if (loadedFromCache) {
|
||||||
binding.mainDrawer.itemAdapter.add(
|
binding.mainDrawer.itemAdapter.add(
|
||||||
@ -589,7 +587,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val filteredHiddenTags: List<Tag> =
|
val filteredHiddenTags: List<SelfossModel.Tag> =
|
||||||
maybeTags.filter { hiddenTags.contains(it.tag) }
|
maybeTags.filter { hiddenTags.contains(it.tag) }
|
||||||
tagsBadge = filteredHiddenTags.map {
|
tagsBadge = filteredHiddenTags.map {
|
||||||
val gd = GradientDrawable()
|
val gd = GradientDrawable()
|
||||||
@ -613,9 +611,9 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
|
|||||||
color = ColorHolder.fromColor(appColors.colorAccent) }
|
color = ColorHolder.fromColor(appColors.colorAccent) }
|
||||||
onDrawerItemClickListener = { _,_,_ ->
|
onDrawerItemClickListener = { _,_,_ ->
|
||||||
allItems = ArrayList()
|
allItems = ArrayList()
|
||||||
SharedItems.tagFilter = it.tag
|
searchService.tagFilter = it.tag
|
||||||
SharedItems.sourceFilter = null
|
searchService.sourceFilter = null
|
||||||
SharedItems.sourceIDFilter = null
|
searchService.sourceIDFilter = null
|
||||||
getElementsAccordingToTab()
|
getElementsAccordingToTab()
|
||||||
fetchOnEmptyList()
|
fetchOnEmptyList()
|
||||||
false
|
false
|
||||||
@ -631,7 +629,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleSources(maybeSources: List<Source>?) {
|
fun handleSources(maybeSources: List<SelfossModel.Source>?) {
|
||||||
if (maybeSources == null) {
|
if (maybeSources == null) {
|
||||||
if (loadedFromCache) {
|
if (loadedFromCache) {
|
||||||
binding.mainDrawer.itemAdapter.add(
|
binding.mainDrawer.itemAdapter.add(
|
||||||
@ -646,12 +644,12 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
|
|||||||
val item = PrimaryDrawerItem().apply {
|
val item = PrimaryDrawerItem().apply {
|
||||||
nameText = source.getTitleDecoded()
|
nameText = source.getTitleDecoded()
|
||||||
identifier = source.id.toLong()
|
identifier = source.id.toLong()
|
||||||
iconUrl = source.getIcon(this@HomeActivity)
|
iconUrl = source.getIcon(apiDetailsService.getBaseUrl())
|
||||||
onDrawerItemClickListener = { _,_,_ ->
|
onDrawerItemClickListener = { _,_,_ ->
|
||||||
allItems = ArrayList()
|
allItems = ArrayList()
|
||||||
SharedItems.sourceIDFilter = source.id.toLong()
|
searchService.sourceIDFilter = source.id.toLong()
|
||||||
SharedItems.sourceFilter = source.title
|
searchService.sourceFilter = source.title
|
||||||
SharedItems.tagFilter = null
|
searchService.tagFilter = null
|
||||||
getElementsAccordingToTab()
|
getElementsAccordingToTab()
|
||||||
fetchOnEmptyList()
|
fetchOnEmptyList()
|
||||||
false
|
false
|
||||||
@ -672,9 +670,9 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
|
|||||||
badgeRes = R.string.drawer_action_clear
|
badgeRes = R.string.drawer_action_clear
|
||||||
onDrawerItemClickListener = { _,_,_ ->
|
onDrawerItemClickListener = { _,_,_ ->
|
||||||
allItems = ArrayList()
|
allItems = ArrayList()
|
||||||
SharedItems.sourceFilter = null
|
searchService.sourceFilter = null
|
||||||
SharedItems.sourceIDFilter = null
|
searchService.sourceIDFilter = null
|
||||||
SharedItems.tagFilter = null
|
searchService.tagFilter = null
|
||||||
binding.mainDrawer.setSelectionAtPosition(-1)
|
binding.mainDrawer.setSelectionAtPosition(-1)
|
||||||
getElementsAccordingToTab()
|
getElementsAccordingToTab()
|
||||||
fetchOnEmptyList()
|
fetchOnEmptyList()
|
||||||
@ -769,47 +767,37 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun drawerApiCalls(maybeDrawerData: DrawerData?) {
|
fun drawerApiCalls(maybeDrawerData: DrawerData?) {
|
||||||
var tags: List<Tag>? = null
|
var tags: List<SelfossModel.Tag>? = null
|
||||||
var sources: List<Source>?
|
var sources: List<SelfossModel.Source>?
|
||||||
|
|
||||||
fun sourcesApiCall() {
|
fun sourcesApiCall() {
|
||||||
if (this@HomeActivity.isNetworkAccessible(null, offlineShortcut) && updateSources) {
|
if (this@HomeActivity.isNetworkAvailable(null, offlineShortcut) && updateSources) {
|
||||||
api.sources.enqueue(object : Callback<List<Source>> {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
override fun onResponse(
|
val response = api.sources()
|
||||||
call: Call<List<Source>>?,
|
if (response != null) {
|
||||||
response: Response<List<Source>>
|
sources = response
|
||||||
) {
|
|
||||||
sources = response.body()
|
|
||||||
val apiDrawerData = DrawerData(tags, sources)
|
val apiDrawerData = DrawerData(tags, sources)
|
||||||
if ((maybeDrawerData != null && maybeDrawerData != apiDrawerData) || maybeDrawerData == null) {
|
if ((maybeDrawerData != null && maybeDrawerData != apiDrawerData) || maybeDrawerData == null) {
|
||||||
handleDrawerData(apiDrawerData)
|
handleDrawerData(apiDrawerData)
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
|
||||||
override fun onFailure(call: Call<List<Source>>?, t: Throwable?) {
|
|
||||||
val apiDrawerData = DrawerData(tags, null)
|
val apiDrawerData = DrawerData(tags, null)
|
||||||
if ((maybeDrawerData != null && maybeDrawerData != apiDrawerData) || maybeDrawerData == null) {
|
if ((maybeDrawerData != null && maybeDrawerData != apiDrawerData) || maybeDrawerData == null) {
|
||||||
handleDrawerData(apiDrawerData)
|
handleDrawerData(apiDrawerData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this@HomeActivity.isNetworkAccessible(null, offlineShortcut) && updateSources) {
|
if (this@HomeActivity.isNetworkAvailable(null, offlineShortcut) && updateSources) {
|
||||||
api.tags.enqueue(object : Callback<List<Tag>> {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
override fun onResponse(
|
val response = api.tags()
|
||||||
call: Call<List<Tag>>,
|
if (response != null) {
|
||||||
response: Response<List<Tag>>
|
tags = response
|
||||||
) {
|
|
||||||
tags = response.body()
|
|
||||||
sourcesApiCall()
|
|
||||||
}
|
}
|
||||||
|
sourcesApiCall()
|
||||||
override fun onFailure(call: Call<List<Tag>>?, t: Throwable?) {
|
}
|
||||||
sourcesApiCall()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -914,9 +902,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
|
|||||||
|
|
||||||
private fun fetchOnEmptyList() {
|
private fun fetchOnEmptyList() {
|
||||||
binding.recyclerView.doOnNextLayout {
|
binding.recyclerView.doOnNextLayout {
|
||||||
if (SharedItems.focusedItems.size - 1 == getLastVisibleItem()) {
|
// Todo:
|
||||||
getElementsAccordingToTab(true)
|
// if (SharedItems.focusedItems.size - 1 == getLastVisibleItem()) {
|
||||||
}
|
// getElementsAccordingToTab(true)
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -968,11 +957,12 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
offset = if (appendResults) {
|
// Todo:
|
||||||
SharedItems.focusedItems.size - 1
|
// offset = if (appendResults) {
|
||||||
} else {
|
// SharedItems.focusedItems.size - 1
|
||||||
0
|
// } else {
|
||||||
}
|
// 0
|
||||||
|
// }
|
||||||
firstVisible = if (appendResults) firstVisible else 0
|
firstVisible = if (appendResults) firstVisible else 0
|
||||||
|
|
||||||
doGetAccordingToTab()
|
doGetAccordingToTab()
|
||||||
@ -980,39 +970,41 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
|
|||||||
|
|
||||||
private fun getUnRead(appendResults: Boolean = false) {
|
private fun getUnRead(appendResults: Boolean = false) {
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
if (appendResults || !SharedItems.fetchedUnread) {
|
// Todo:
|
||||||
binding.swipeRefreshLayout.isRefreshing = true
|
// if (appendResults || !SharedItems.fetchedUnread) {
|
||||||
getUnreadItems(applicationContext, api, db, itemsNumber, offset)
|
// binding.swipeRefreshLayout.isRefreshing = true
|
||||||
binding.swipeRefreshLayout.isRefreshing = false
|
// service.getUnreadItems(itemsNumber, offset, applicationContext.isNetworkAvailable())
|
||||||
}
|
// binding.swipeRefreshLayout.isRefreshing = false
|
||||||
SharedItems.getUnRead()
|
// }
|
||||||
items = SharedItems.focusedItems
|
// Todo: SharedItems.getUnRead()
|
||||||
|
// Todo: items = SharedItems.focusedItems
|
||||||
handleListResult()
|
handleListResult()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getRead(appendResults: Boolean = false) {
|
private fun getRead(appendResults: Boolean = false) {
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
if (appendResults || !SharedItems.fetchedAll) {
|
// Todo:
|
||||||
binding.swipeRefreshLayout.isRefreshing = true
|
// if (appendResults || !SharedItems.fetchedAll) {
|
||||||
getReadItems(applicationContext, api, db, itemsNumber, offset)
|
// binding.swipeRefreshLayout.isRefreshing = true
|
||||||
binding.swipeRefreshLayout.isRefreshing = false
|
// service.getReadItems(itemsNumber, offset, applicationContext.isNetworkAvailable())
|
||||||
}
|
// binding.swipeRefreshLayout.isRefreshing = false
|
||||||
SharedItems.getAll()
|
// }
|
||||||
items = SharedItems.focusedItems
|
// SharedItems.getAll()
|
||||||
|
// items = SharedItems.focusedItems
|
||||||
handleListResult()
|
handleListResult()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getStarred(appendResults: Boolean = false) {
|
private fun getStarred(appendResults: Boolean = false) {
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
if (appendResults || !SharedItems.fetchedStarred) {
|
if (appendResults || !searchService.fetchedStarred) {
|
||||||
binding.swipeRefreshLayout.isRefreshing = true
|
binding.swipeRefreshLayout.isRefreshing = true
|
||||||
getStarredItems(applicationContext, api, db, itemsNumber, offset)
|
service.getStarredItems(itemsNumber, offset, applicationContext.isNetworkAvailable())
|
||||||
binding.swipeRefreshLayout.isRefreshing = false
|
binding.swipeRefreshLayout.isRefreshing = false
|
||||||
}
|
}
|
||||||
SharedItems.getStarred()
|
// Todo: SharedItems.getStarred()
|
||||||
items = SharedItems.focusedItems
|
// Todo: items = SharedItems.focusedItems
|
||||||
handleListResult()
|
handleListResult()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1036,6 +1028,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
|
|||||||
this,
|
this,
|
||||||
items,
|
items,
|
||||||
api,
|
api,
|
||||||
|
apiDetailsService,
|
||||||
db,
|
db,
|
||||||
customTabActivityHelper,
|
customTabActivityHelper,
|
||||||
internalBrowser,
|
internalBrowser,
|
||||||
@ -1043,7 +1036,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
|
|||||||
fullHeightCards,
|
fullHeightCards,
|
||||||
appColors,
|
appColors,
|
||||||
userIdentifier,
|
userIdentifier,
|
||||||
config
|
config,
|
||||||
|
searchService
|
||||||
) {
|
) {
|
||||||
updateItems(it)
|
updateItems(it)
|
||||||
}
|
}
|
||||||
@ -1053,13 +1047,15 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
|
|||||||
this,
|
this,
|
||||||
items,
|
items,
|
||||||
api,
|
api,
|
||||||
|
apiDetailsService,
|
||||||
db,
|
db,
|
||||||
customTabActivityHelper,
|
customTabActivityHelper,
|
||||||
internalBrowser,
|
internalBrowser,
|
||||||
articleViewer,
|
articleViewer,
|
||||||
userIdentifier,
|
userIdentifier,
|
||||||
appColors,
|
appColors,
|
||||||
config
|
config,
|
||||||
|
searchService
|
||||||
) {
|
) {
|
||||||
updateItems(it)
|
updateItems(it)
|
||||||
}
|
}
|
||||||
@ -1083,7 +1079,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
|
|||||||
private fun reloadBadges() {
|
private fun reloadBadges() {
|
||||||
if (displayUnreadCount || displayAllCount) {
|
if (displayUnreadCount || displayAllCount) {
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
reloadBadges(applicationContext, api)
|
service.reloadBadges(applicationContext.isNetworkAvailable())
|
||||||
reloadBadgeContent()
|
reloadBadgeContent()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1092,15 +1088,15 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
|
|||||||
private fun reloadBadgeContent() {
|
private fun reloadBadgeContent() {
|
||||||
if (displayUnreadCount) {
|
if (displayUnreadCount) {
|
||||||
tabNewBadge
|
tabNewBadge
|
||||||
.setText(SharedItems.badgeUnread.toString())
|
.setText(searchService.badgeUnread.toString())
|
||||||
.maybeShow()
|
.maybeShow()
|
||||||
}
|
}
|
||||||
if (displayAllCount) {
|
if (displayAllCount) {
|
||||||
tabArchiveBadge
|
tabArchiveBadge
|
||||||
.setText(SharedItems.badgeAll.toString())
|
.setText(searchService.badgeAll.toString())
|
||||||
.maybeShow()
|
.maybeShow()
|
||||||
tabStarredBadge
|
tabStarredBadge
|
||||||
.setText(SharedItems.badgeStarred.toString())
|
.setText(searchService.badgeStarred.toString())
|
||||||
.maybeShow()
|
.maybeShow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1120,7 +1116,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
|
|||||||
|
|
||||||
override fun onQueryTextChange(p0: String?): Boolean {
|
override fun onQueryTextChange(p0: String?): Boolean {
|
||||||
if (p0.isNullOrBlank()) {
|
if (p0.isNullOrBlank()) {
|
||||||
SharedItems.searchFilter = null
|
searchService.searchFilter = null
|
||||||
getElementsAccordingToTab()
|
getElementsAccordingToTab()
|
||||||
fetchOnEmptyList()
|
fetchOnEmptyList()
|
||||||
}
|
}
|
||||||
@ -1128,7 +1124,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onQueryTextSubmit(p0: String?): Boolean {
|
override fun onQueryTextSubmit(p0: String?): Boolean {
|
||||||
SharedItems.searchFilter = p0
|
searchService.searchFilter = p0
|
||||||
getElementsAccordingToTab()
|
getElementsAccordingToTab()
|
||||||
fetchOnEmptyList()
|
fetchOnEmptyList()
|
||||||
return false
|
return false
|
||||||
@ -1158,29 +1154,25 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
|
|||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
R.id.refresh -> {
|
R.id.refresh -> {
|
||||||
if (this@HomeActivity.isNetworkAccessible(null, offlineShortcut)) {
|
if (this@HomeActivity.isNetworkAvailable(null, offlineShortcut)) {
|
||||||
needsConfirmation(R.string.menu_home_refresh, R.string.refresh_dialog_message) {
|
needsConfirmation(R.string.menu_home_refresh, R.string.refresh_dialog_message) {
|
||||||
api.update().enqueue(object : Callback<String> {
|
Toast.makeText(this, R.string.refresh_in_progress, Toast.LENGTH_SHORT).show()
|
||||||
override fun onResponse(
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
call: Call<String>,
|
val status = api.update()
|
||||||
response: Response<String>
|
if (status != null && status.isSuccess) {
|
||||||
) {
|
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
this@HomeActivity,
|
this@HomeActivity,
|
||||||
R.string.refresh_success_response, Toast.LENGTH_LONG
|
R.string.refresh_success_response, Toast.LENGTH_LONG
|
||||||
)
|
)
|
||||||
.show()
|
.show()
|
||||||
}
|
} else {
|
||||||
|
|
||||||
override fun onFailure(call: Call<String>, t: Throwable) {
|
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
this@HomeActivity,
|
this@HomeActivity,
|
||||||
R.string.refresh_failer_message,
|
R.string.refresh_failer_message,
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
Toast.makeText(this, R.string.refresh_in_progress, Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
@ -1192,9 +1184,9 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
|
|||||||
needsConfirmation(R.string.readAll, R.string.markall_dialog_message) {
|
needsConfirmation(R.string.readAll, R.string.markall_dialog_message) {
|
||||||
binding.swipeRefreshLayout.isRefreshing = true
|
binding.swipeRefreshLayout.isRefreshing = true
|
||||||
|
|
||||||
if (this@HomeActivity.isNetworkAccessible(null, offlineShortcut)) {
|
if (this@HomeActivity.isNetworkAvailable(null, offlineShortcut)) {
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
val success = readAll(applicationContext, api, db)
|
val success = service.readAll(applicationContext.isNetworkAvailable())
|
||||||
if (success) {
|
if (success) {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
this@HomeActivity,
|
this@HomeActivity,
|
||||||
@ -1230,13 +1222,13 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
|
|||||||
|
|
||||||
private fun maxItemNumber(): Int =
|
private fun maxItemNumber(): Int =
|
||||||
when (elementsShown) {
|
when (elementsShown) {
|
||||||
UNREAD_SHOWN -> SharedItems.badgeUnread
|
UNREAD_SHOWN -> searchService.badgeUnread
|
||||||
READ_SHOWN -> SharedItems.badgeAll
|
READ_SHOWN -> searchService.badgeAll
|
||||||
FAV_SHOWN -> SharedItems.badgeStarred
|
FAV_SHOWN -> searchService.badgeStarred
|
||||||
else -> SharedItems.badgeUnread // if !elementsShown then unread are fetched.
|
else -> searchService.badgeUnread // if !elementsShown then unread are fetched.
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateItems(adapterItems: ArrayList<Item>) {
|
private fun updateItems(adapterItems: ArrayList<SelfossModel.Item>) {
|
||||||
items = adapterItems
|
items = adapterItems
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1259,32 +1251,24 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handleOfflineActions() {
|
private fun handleOfflineActions() {
|
||||||
fun <T>doAndReportOnFail(call: Call<T>, action: ActionEntity) {
|
fun doAndReportOnFail(call: SelfossModel.SuccessResponse?, action: ActionEntity) {
|
||||||
call.enqueue(object: Callback<T> {
|
if (call != null && call.isSuccess) {
|
||||||
override fun onResponse(
|
thread {
|
||||||
call: Call<T>,
|
db.actionsDao().delete(action)
|
||||||
response: Response<T>
|
}
|
||||||
) {
|
}
|
||||||
thread {
|
|
||||||
db.actionsDao().delete(action)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(call: Call<T>, t: Throwable) {
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this@HomeActivity.isNetworkAccessible(null, offlineShortcut)) {
|
if (this@HomeActivity.isNetworkAvailable(null, offlineShortcut)) {
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
val actions = db.actionsDao().actions()
|
val actions = db.actionsDao().actions()
|
||||||
|
|
||||||
actions.forEach { action ->
|
actions.forEach { action ->
|
||||||
when {
|
when {
|
||||||
action.read -> doAndReportOnFail(api.markItem(action.articleId), action)
|
action.read -> doAndReportOnFail(api.markAsRead(action.articleId), action)
|
||||||
action.unread -> doAndReportOnFail(api.unmarkItem(action.articleId), action)
|
action.unread -> doAndReportOnFail(api.unmarkAsRead(action.articleId), action)
|
||||||
action.starred -> doAndReportOnFail(api.starrItem(action.articleId), action)
|
action.starred -> doAndReportOnFail(api.starr(action.articleId), action)
|
||||||
action.unstarred -> doAndReportOnFail(api.unstarrItem(action.articleId), action)
|
action.unstarred -> doAndReportOnFail(api.unstarr(action.articleId), action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,19 +8,26 @@ import android.os.Bundle
|
|||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
|
import android.util.Log
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.inputmethod.EditorInfo
|
import android.view.inputmethod.EditorInfo
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import bou.amine.apps.readerforselfossv2.android.api.selfoss.SelfossApi
|
import androidx.work.Logger
|
||||||
import bou.amine.apps.readerforselfossv2.android.api.selfoss.SuccessResponse
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityLoginBinding
|
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityLoginBinding
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.service.AndroidApiDetailsService
|
||||||
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
|
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlValid
|
import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlValid
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAccessible
|
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable
|
||||||
|
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
|
||||||
|
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
|
||||||
import com.mikepenz.aboutlibraries.LibsBuilder
|
import com.mikepenz.aboutlibraries.LibsBuilder
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
import retrofit2.Callback
|
import retrofit2.Callback
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
@ -120,6 +127,21 @@ class LoginActivity : AppCompatActivity() {
|
|||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun preferenceError(t: Throwable) {
|
||||||
|
editor.remove("url")
|
||||||
|
editor.remove("login")
|
||||||
|
editor.remove("httpUserName")
|
||||||
|
editor.remove("password")
|
||||||
|
editor.remove("httpPassword")
|
||||||
|
editor.apply()
|
||||||
|
binding.urlView.error = getString(R.string.wrong_infos)
|
||||||
|
binding.loginView.error = getString(R.string.wrong_infos)
|
||||||
|
binding.passwordView.error = getString(R.string.wrong_infos)
|
||||||
|
binding.httpLoginView.error = getString(R.string.wrong_infos)
|
||||||
|
binding.httpPasswordView.error = getString(R.string.wrong_infos)
|
||||||
|
showProgress(false)
|
||||||
|
}
|
||||||
|
|
||||||
private fun attemptLogin() {
|
private fun attemptLogin() {
|
||||||
|
|
||||||
// Reset errors.
|
// Reset errors.
|
||||||
@ -198,45 +220,28 @@ class LoginActivity : AppCompatActivity() {
|
|||||||
editor.putBoolean("isSelfSignedCert", isWithSelfSignedCert)
|
editor.putBoolean("isSelfSignedCert", isWithSelfSignedCert)
|
||||||
editor.apply()
|
editor.apply()
|
||||||
|
|
||||||
|
val apiDetailsService = AndroidApiDetailsService(this@LoginActivity)
|
||||||
val api = SelfossApi(
|
val api = SelfossApi(
|
||||||
this,
|
// this,
|
||||||
this@LoginActivity,
|
// this@LoginActivity,
|
||||||
isWithSelfSignedCert,
|
// isWithSelfSignedCert,
|
||||||
-1L
|
// -1L
|
||||||
|
apiDetailsService
|
||||||
)
|
)
|
||||||
|
|
||||||
if (this@LoginActivity.isNetworkAccessible(this@LoginActivity.findViewById(R.id.loginForm))) {
|
if (this@LoginActivity.isNetworkAvailable(this@LoginActivity.findViewById(R.id.loginForm))) {
|
||||||
api.login().enqueue(object : Callback<SuccessResponse> {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
private fun preferenceError(t: Throwable) {
|
try {
|
||||||
editor.remove("url")
|
val result = api.login()
|
||||||
editor.remove("login")
|
if (result != null && result.isSuccess) {
|
||||||
editor.remove("httpUserName")
|
|
||||||
editor.remove("password")
|
|
||||||
editor.remove("httpPassword")
|
|
||||||
editor.apply()
|
|
||||||
binding.urlView.error = getString(R.string.wrong_infos)
|
|
||||||
binding.loginView.error = getString(R.string.wrong_infos)
|
|
||||||
binding.passwordView.error = getString(R.string.wrong_infos)
|
|
||||||
binding.httpLoginView.error = getString(R.string.wrong_infos)
|
|
||||||
binding.httpPasswordView.error = getString(R.string.wrong_infos)
|
|
||||||
showProgress(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResponse(
|
|
||||||
call: Call<SuccessResponse>,
|
|
||||||
response: Response<SuccessResponse>
|
|
||||||
) {
|
|
||||||
if (response.body() != null && response.body()!!.isSuccess) {
|
|
||||||
goToMain()
|
goToMain()
|
||||||
} else {
|
} else {
|
||||||
preferenceError(Exception("No response body..."))
|
preferenceError(Exception("Not success"))
|
||||||
}
|
}
|
||||||
|
} catch (cause: Throwable) {
|
||||||
|
Log.e("1", "LOL")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
|
|
||||||
preferenceError(t)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
showProgress(false)
|
showProgress(false)
|
||||||
}
|
}
|
||||||
|
@ -5,29 +5,33 @@ import android.content.SharedPreferences
|
|||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import androidx.preference.PreferenceManager
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.room.Room
|
import androidx.room.Room
|
||||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
import androidx.viewpager2.widget.ViewPager2
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
import bou.amine.apps.readerforselfossv2.android.api.selfoss.Item
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.api.selfoss.SelfossApi
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityReaderBinding
|
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityReaderBinding
|
||||||
import bou.amine.apps.readerforselfossv2.android.fragments.ArticleFragment
|
import bou.amine.apps.readerforselfossv2.android.fragments.ArticleFragment
|
||||||
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
|
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
|
||||||
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_1_2
|
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_1_2
|
||||||
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_2_3
|
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_2_3
|
||||||
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_3_4
|
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_3_4
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.service.AndroidApiDetailsService
|
||||||
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
|
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
|
||||||
import bou.amine.apps.readerforselfossv2.android.themes.Toppings
|
import bou.amine.apps.readerforselfossv2.android.themes.Toppings
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.SharedItems
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.toggleStar
|
import bou.amine.apps.readerforselfossv2.android.utils.toggleStar
|
||||||
|
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
|
||||||
|
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||||
|
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
|
||||||
import com.ftinc.scoop.Scoop
|
import com.ftinc.scoop.Scoop
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class ReaderActivity : AppCompatActivity() {
|
class ReaderActivity : AppCompatActivity() {
|
||||||
|
|
||||||
@ -98,10 +102,11 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
activeAlignment = prefs.getInt("text_align", JUSTIFY)
|
activeAlignment = prefs.getInt("text_align", JUSTIFY)
|
||||||
|
|
||||||
api = SelfossApi(
|
api = SelfossApi(
|
||||||
this,
|
// this,
|
||||||
this@ReaderActivity,
|
// this@ReaderActivity,
|
||||||
settings.getBoolean("isSelfSignedCert", false),
|
// settings.getBoolean("isSelfSignedCert", false),
|
||||||
prefs.getString("api_timeout", "-1")!!.toLong()
|
// prefs.getString("api_timeout", "-1")!!.toLong()
|
||||||
|
AndroidApiDetailsService(this@ReaderActivity)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (allItems.isEmpty()) {
|
if (allItems.isEmpty()) {
|
||||||
@ -122,9 +127,11 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
binding.indicator.setViewPager(binding.pager)
|
binding.indicator.setViewPager(binding.pager)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun readItem(item: Item) {
|
private fun readItem(item: SelfossModel.Item) {
|
||||||
if (markOnScroll) {
|
if (markOnScroll) {
|
||||||
SharedItems.readItem(applicationContext, api, db, item)
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
// Todo: SharedItems.readItem(applicationContext, api, db, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,7 +177,7 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
inflater.inflate(R.menu.reader_menu, menu)
|
inflater.inflate(R.menu.reader_menu, menu)
|
||||||
toolbarMenu = menu
|
toolbarMenu = menu
|
||||||
|
|
||||||
if (allItems.isNotEmpty() && allItems[currentItem].starred) {
|
if (allItems.isNotEmpty() && allItems[currentItem].starred == 1) {
|
||||||
canRemoveFromFavorite()
|
canRemoveFromFavorite()
|
||||||
} else {
|
} else {
|
||||||
canFavorite()
|
canFavorite()
|
||||||
@ -187,7 +194,7 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
override fun onPageSelected(position: Int) {
|
override fun onPageSelected(position: Int) {
|
||||||
super.onPageSelected(position)
|
super.onPageSelected(position)
|
||||||
|
|
||||||
if (allItems[position].starred) {
|
if (allItems[position].starred == 1) {
|
||||||
canRemoveFromFavorite()
|
canRemoveFromFavorite()
|
||||||
} else {
|
} else {
|
||||||
canFavorite()
|
canFavorite()
|
||||||
@ -218,21 +225,27 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
R.id.star -> {
|
R.id.star -> {
|
||||||
if (allItems[binding.pager.currentItem].starred) {
|
if (allItems[binding.pager.currentItem].starred == 1) {
|
||||||
SharedItems.unstarItem(
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
this@ReaderActivity,
|
// Todo:
|
||||||
api,
|
// SharedItems.unstarItem(
|
||||||
db,
|
// this@ReaderActivity,
|
||||||
allItems[binding.pager.currentItem]
|
// api,
|
||||||
)
|
// db,
|
||||||
|
// allItems[binding.pager.currentItem]
|
||||||
|
// )
|
||||||
|
}
|
||||||
afterUnsave()
|
afterUnsave()
|
||||||
} else {
|
} else {
|
||||||
SharedItems.starItem(
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
this@ReaderActivity,
|
// Todo:
|
||||||
api,
|
// SharedItems.starItem(
|
||||||
db,
|
// this@ReaderActivity,
|
||||||
allItems[binding.pager.currentItem]
|
// api,
|
||||||
)
|
// db,
|
||||||
|
// allItems[binding.pager.currentItem]
|
||||||
|
// )
|
||||||
|
}
|
||||||
afterSave()
|
afterSave()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -260,6 +273,6 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
var allItems: ArrayList<Item> = ArrayList()
|
var allItems: ArrayList<SelfossModel.Item> = ArrayList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,17 +9,23 @@ import androidx.appcompat.app.AppCompatActivity
|
|||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import bou.amine.apps.readerforselfossv2.android.adapters.SourcesListAdapter
|
import bou.amine.apps.readerforselfossv2.android.adapters.SourcesListAdapter
|
||||||
import bou.amine.apps.readerforselfossv2.android.api.selfoss.SelfossApi
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.api.selfoss.Source
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.databinding.ActivitySourcesBinding
|
import bou.amine.apps.readerforselfossv2.android.databinding.ActivitySourcesBinding
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.service.AndroidApiDetailsService
|
||||||
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
|
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
|
||||||
import bou.amine.apps.readerforselfossv2.android.themes.Toppings
|
import bou.amine.apps.readerforselfossv2.android.themes.Toppings
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAccessible
|
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable
|
||||||
|
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
|
||||||
|
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||||
|
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
|
||||||
import com.ftinc.scoop.Scoop
|
import com.ftinc.scoop.Scoop
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
import retrofit2.Callback
|
import retrofit2.Callback
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
|
import java.util.ArrayList
|
||||||
|
|
||||||
class SourcesActivity : AppCompatActivity() {
|
class SourcesActivity : AppCompatActivity() {
|
||||||
|
|
||||||
@ -60,27 +66,27 @@ class SourcesActivity : AppCompatActivity() {
|
|||||||
getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
|
getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
|
||||||
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
|
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
|
|
||||||
|
val apiDetailsService = AndroidApiDetailsService(this@SourcesActivity)
|
||||||
val api = SelfossApi(
|
val api = SelfossApi(
|
||||||
this,
|
// this,
|
||||||
this@SourcesActivity,
|
// this@SourcesActivity,
|
||||||
settings.getBoolean("isSelfSignedCert", false),
|
// settings.getBoolean("isSelfSignedCert", false),
|
||||||
prefs.getString("api_timeout", "-1")!!.toLong()
|
// prefs.getString("api_timeout", "-1")!!.toLong()
|
||||||
|
apiDetailsService
|
||||||
)
|
)
|
||||||
var items: ArrayList<Source> = ArrayList()
|
var items: ArrayList<SelfossModel.Source>
|
||||||
|
|
||||||
binding.recyclerView.setHasFixedSize(true)
|
binding.recyclerView.setHasFixedSize(true)
|
||||||
binding.recyclerView.layoutManager = mLayoutManager
|
binding.recyclerView.layoutManager = mLayoutManager
|
||||||
|
|
||||||
if (this@SourcesActivity.isNetworkAccessible(binding.recyclerView)) {
|
if (this@SourcesActivity.isNetworkAvailable(binding.recyclerView)) {
|
||||||
api.sources.enqueue(object : Callback<List<Source>> {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
override fun onResponse(
|
val response = api.sources()
|
||||||
call: Call<List<Source>>,
|
if (response != null) {
|
||||||
response: Response<List<Source>>
|
items = response
|
||||||
) {
|
val mAdapter = SourcesListAdapter(this@SourcesActivity, items, api,
|
||||||
if (response.body() != null && response.body()!!.isNotEmpty()) {
|
apiDetailsService
|
||||||
items = response.body() as ArrayList<Source>
|
)
|
||||||
}
|
|
||||||
val mAdapter = SourcesListAdapter(this@SourcesActivity, items, api)
|
|
||||||
binding.recyclerView.adapter = mAdapter
|
binding.recyclerView.adapter = mAdapter
|
||||||
mAdapter.notifyDataSetChanged()
|
mAdapter.notifyDataSetChanged()
|
||||||
if (items.isEmpty()) {
|
if (items.isEmpty()) {
|
||||||
@ -90,16 +96,14 @@ class SourcesActivity : AppCompatActivity() {
|
|||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
|
||||||
override fun onFailure(call: Call<List<Source>>, t: Throwable) {
|
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
this@SourcesActivity,
|
this@SourcesActivity,
|
||||||
R.string.cant_get_sources,
|
R.string.cant_get_sources,
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.fab.setOnClickListener {
|
binding.fab.setOnClickListener {
|
||||||
|
@ -2,38 +2,38 @@ package bou.amine.apps.readerforselfossv2.android.adapters
|
|||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView.ScaleType
|
import android.widget.ImageView.ScaleType
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import bou.amine.apps.readerforselfossv2.android.R
|
import bou.amine.apps.readerforselfossv2.android.R
|
||||||
import bou.amine.apps.readerforselfossv2.android.api.selfoss.Item
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.api.selfoss.SelfossApi
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.databinding.CardItemBinding
|
import bou.amine.apps.readerforselfossv2.android.databinding.CardItemBinding
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.model.*
|
||||||
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
|
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
|
||||||
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
|
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
import bou.amine.apps.readerforselfossv2.android.utils.*
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.LinkOnTouchListener
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.SharedItems
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.buildCustomTabsIntent
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper
|
import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.bitmapCenterCrop
|
import bou.amine.apps.readerforselfossv2.android.utils.glide.bitmapCenterCrop
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularBitmapDrawable
|
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularBitmapDrawable
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable
|
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.openInBrowserAsNewTask
|
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.openItemUrl
|
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.shareLink
|
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.sourceAndDateText
|
import bou.amine.apps.readerforselfossv2.service.SearchService
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.toTextDrawableString
|
import bou.amine.apps.readerforselfossv2.utils.DateUtils
|
||||||
import com.amulyakhare.textdrawable.TextDrawable
|
import com.amulyakhare.textdrawable.TextDrawable
|
||||||
import com.amulyakhare.textdrawable.util.ColorGenerator
|
import com.amulyakhare.textdrawable.util.ColorGenerator
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class ItemCardAdapter(
|
class ItemCardAdapter(
|
||||||
override val app: Activity,
|
override val app: Activity,
|
||||||
override var items: ArrayList<Item>,
|
override var items: ArrayList<SelfossModel.Item>,
|
||||||
override val api: SelfossApi,
|
override val api: SelfossApi,
|
||||||
|
override val apiDetailsService: ApiDetailsService,
|
||||||
override val db: AppDatabase,
|
override val db: AppDatabase,
|
||||||
private val helper: CustomTabActivityHelper,
|
private val helper: CustomTabActivityHelper,
|
||||||
private val internalBrowser: Boolean,
|
private val internalBrowser: Boolean,
|
||||||
@ -42,7 +42,8 @@ class ItemCardAdapter(
|
|||||||
override val appColors: AppColors,
|
override val appColors: AppColors,
|
||||||
override val userIdentifier: String,
|
override val userIdentifier: String,
|
||||||
override val config: Config,
|
override val config: Config,
|
||||||
override val updateItems: (ArrayList<Item>) -> Unit
|
override val searchService: SearchService,
|
||||||
|
override val updateItems: (ArrayList<SelfossModel.Item>) -> Unit
|
||||||
) : ItemsAdapter<ItemCardAdapter.ViewHolder>() {
|
) : ItemsAdapter<ItemCardAdapter.ViewHolder>() {
|
||||||
private val c: Context = app.baseContext
|
private val c: Context = app.baseContext
|
||||||
private val generator: ColorGenerator = ColorGenerator.MATERIAL
|
private val generator: ColorGenerator = ColorGenerator.MATERIAL
|
||||||
@ -58,30 +59,30 @@ class ItemCardAdapter(
|
|||||||
with(holder) {
|
with(holder) {
|
||||||
val itm = items[position]
|
val itm = items[position]
|
||||||
|
|
||||||
binding.favButton.isSelected = itm.starred
|
binding.favButton.isSelected = itm.starred == 1
|
||||||
binding.title.text = itm.getTitleDecoded()
|
binding.title.text = itm.getTitleDecoded()
|
||||||
|
|
||||||
binding.title.setOnTouchListener(LinkOnTouchListener())
|
binding.title.setOnTouchListener(LinkOnTouchListener())
|
||||||
|
|
||||||
binding.title.setLinkTextColor(appColors.colorAccent)
|
binding.title.setLinkTextColor(appColors.colorAccent)
|
||||||
|
|
||||||
binding.sourceTitleAndDate.text = itm.sourceAndDateText()
|
binding.sourceTitleAndDate.text = itm.sourceAndDateText(DateUtils(apiDetailsService))
|
||||||
|
|
||||||
if (!fullHeightCards) {
|
if (!fullHeightCards) {
|
||||||
binding.itemImage.maxHeight = imageMaxHeight
|
binding.itemImage.maxHeight = imageMaxHeight
|
||||||
binding.itemImage.scaleType = ScaleType.CENTER_CROP
|
binding.itemImage.scaleType = ScaleType.CENTER_CROP
|
||||||
}
|
}
|
||||||
|
|
||||||
if (itm.getThumbnail(c).isEmpty()) {
|
if (itm.getThumbnail(apiDetailsService.getBaseUrl()).isEmpty()) {
|
||||||
binding.itemImage.visibility = View.GONE
|
binding.itemImage.visibility = View.GONE
|
||||||
Glide.with(c).clear(binding.itemImage)
|
Glide.with(c).clear(binding.itemImage)
|
||||||
binding.itemImage.setImageDrawable(null)
|
binding.itemImage.setImageDrawable(null)
|
||||||
} else {
|
} else {
|
||||||
binding.itemImage.visibility = View.VISIBLE
|
binding.itemImage.visibility = View.VISIBLE
|
||||||
c.bitmapCenterCrop(config, itm.getThumbnail(c), binding.itemImage)
|
c.bitmapCenterCrop(config, itm.getThumbnail(apiDetailsService.getBaseUrl()), binding.itemImage)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (itm.getIcon(c).isEmpty()) {
|
if (itm.getIcon(apiDetailsService.getBaseUrl()).isEmpty()) {
|
||||||
val color = generator.getColor(itm.getSourceTitle())
|
val color = generator.getColor(itm.getSourceTitle())
|
||||||
|
|
||||||
val drawable =
|
val drawable =
|
||||||
@ -91,7 +92,7 @@ class ItemCardAdapter(
|
|||||||
.build(itm.getSourceTitle().toTextDrawableString(c), color)
|
.build(itm.getSourceTitle().toTextDrawableString(c), color)
|
||||||
binding.sourceImage.setImageDrawable(drawable)
|
binding.sourceImage.setImageDrawable(drawable)
|
||||||
} else {
|
} else {
|
||||||
c.circularBitmapDrawable(config, itm.getIcon(c), binding.sourceImage)
|
c.circularBitmapDrawable(config, itm.getIcon(apiDetailsService.getBaseUrl()), binding.sourceImage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -110,14 +111,18 @@ class ItemCardAdapter(
|
|||||||
|
|
||||||
binding.favButton.setOnClickListener {
|
binding.favButton.setOnClickListener {
|
||||||
val item = items[bindingAdapterPosition]
|
val item = items[bindingAdapterPosition]
|
||||||
if (isNetworkAvailable(c)) {
|
if (c.isNetworkAvailable()) {
|
||||||
if (item.starred) {
|
if (item.starred == 1) {
|
||||||
SharedItems.unstarItem(c, api, db, item)
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
item.starred = false
|
// Todo: SharedItems.unstarItem(c, api, db, item)
|
||||||
|
}
|
||||||
|
item.starred = 0
|
||||||
binding.favButton.isSelected = false
|
binding.favButton.isSelected = false
|
||||||
} else {
|
} else {
|
||||||
SharedItems.starItem(c, api, db, item)
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
item.starred = true
|
// Todo: SharedItems.starItem(c, api, db, item)
|
||||||
|
}
|
||||||
|
item.starred = 1
|
||||||
binding.favButton.isSelected = true
|
binding.favButton.isSelected = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -145,7 +150,8 @@ class ItemCardAdapter(
|
|||||||
customTabsIntent,
|
customTabsIntent,
|
||||||
internalBrowser,
|
internalBrowser,
|
||||||
articleViewer,
|
articleViewer,
|
||||||
app
|
app,
|
||||||
|
searchService
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,31 +2,30 @@ package bou.amine.apps.readerforselfossv2.android.adapters
|
|||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import bou.amine.apps.readerforselfossv2.android.api.selfoss.Item
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import bou.amine.apps.readerforselfossv2.android.api.selfoss.SelfossApi
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.databinding.ListItemBinding
|
import bou.amine.apps.readerforselfossv2.android.databinding.ListItemBinding
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.model.*
|
||||||
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
|
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
|
||||||
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
|
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
import bou.amine.apps.readerforselfossv2.android.utils.*
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.LinkOnTouchListener
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.buildCustomTabsIntent
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper
|
import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.bitmapCenterCrop
|
import bou.amine.apps.readerforselfossv2.android.utils.glide.bitmapCenterCrop
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularBitmapDrawable
|
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularBitmapDrawable
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.openItemUrl
|
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.sourceAndDateText
|
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.toTextDrawableString
|
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
|
||||||
|
import bou.amine.apps.readerforselfossv2.service.SearchService
|
||||||
|
import bou.amine.apps.readerforselfossv2.utils.DateUtils
|
||||||
import com.amulyakhare.textdrawable.TextDrawable
|
import com.amulyakhare.textdrawable.TextDrawable
|
||||||
import com.amulyakhare.textdrawable.util.ColorGenerator
|
import com.amulyakhare.textdrawable.util.ColorGenerator
|
||||||
import kotlin.collections.ArrayList
|
|
||||||
|
|
||||||
class ItemListAdapter(
|
class ItemListAdapter(
|
||||||
override val app: Activity,
|
override val app: Activity,
|
||||||
override var items: ArrayList<Item>,
|
override var items: ArrayList<SelfossModel.Item>,
|
||||||
override val api: SelfossApi,
|
override val api: SelfossApi,
|
||||||
|
override val apiDetailsService: ApiDetailsService,
|
||||||
override val db: AppDatabase,
|
override val db: AppDatabase,
|
||||||
private val helper: CustomTabActivityHelper,
|
private val helper: CustomTabActivityHelper,
|
||||||
private val internalBrowser: Boolean,
|
private val internalBrowser: Boolean,
|
||||||
@ -34,7 +33,8 @@ class ItemListAdapter(
|
|||||||
override val userIdentifier: String,
|
override val userIdentifier: String,
|
||||||
override val appColors: AppColors,
|
override val appColors: AppColors,
|
||||||
override val config: Config,
|
override val config: Config,
|
||||||
override val updateItems: (ArrayList<Item>) -> Unit
|
override val searchService: SearchService,
|
||||||
|
override val updateItems: (ArrayList<SelfossModel.Item>) -> Unit
|
||||||
) : ItemsAdapter<ItemListAdapter.ViewHolder>() {
|
) : ItemsAdapter<ItemListAdapter.ViewHolder>() {
|
||||||
private val generator: ColorGenerator = ColorGenerator.MATERIAL
|
private val generator: ColorGenerator = ColorGenerator.MATERIAL
|
||||||
private val c: Context = app.baseContext
|
private val c: Context = app.baseContext
|
||||||
@ -54,11 +54,11 @@ class ItemListAdapter(
|
|||||||
|
|
||||||
binding.title.setLinkTextColor(appColors.colorAccent)
|
binding.title.setLinkTextColor(appColors.colorAccent)
|
||||||
|
|
||||||
binding.sourceTitleAndDate.text = itm.sourceAndDateText()
|
binding.sourceTitleAndDate.text = itm.sourceAndDateText(DateUtils(apiDetailsService))
|
||||||
|
|
||||||
if (itm.getThumbnail(c).isEmpty()) {
|
if (itm.getThumbnail(apiDetailsService.getBaseUrl()).isEmpty()) {
|
||||||
|
|
||||||
if (itm.getIcon(c).isEmpty()) {
|
if (itm.getIcon(apiDetailsService.getBaseUrl()).isEmpty()) {
|
||||||
val color = generator.getColor(itm.getSourceTitle())
|
val color = generator.getColor(itm.getSourceTitle())
|
||||||
|
|
||||||
val drawable =
|
val drawable =
|
||||||
@ -69,10 +69,10 @@ class ItemListAdapter(
|
|||||||
|
|
||||||
binding.itemImage.setImageDrawable(drawable)
|
binding.itemImage.setImageDrawable(drawable)
|
||||||
} else {
|
} else {
|
||||||
c.circularBitmapDrawable(config, itm.getIcon(c), binding.itemImage)
|
c.circularBitmapDrawable(config, itm.getIcon(apiDetailsService.getBaseUrl()), binding.itemImage)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
c.bitmapCenterCrop(config, itm.getThumbnail(c), binding.itemImage)
|
c.bitmapCenterCrop(config, itm.getThumbnail(apiDetailsService.getBaseUrl()), binding.itemImage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -97,7 +97,8 @@ class ItemListAdapter(
|
|||||||
customTabsIntent,
|
customTabsIntent,
|
||||||
internalBrowser,
|
internalBrowser,
|
||||||
articleViewer,
|
articleViewer,
|
||||||
app
|
app,
|
||||||
|
searchService
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,31 +5,37 @@ import android.graphics.Color
|
|||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import bou.amine.apps.readerforselfossv2.android.R
|
import bou.amine.apps.readerforselfossv2.android.R
|
||||||
import bou.amine.apps.readerforselfossv2.android.api.selfoss.Item
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.api.selfoss.SelfossApi
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
|
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
|
||||||
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
|
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.SharedItems
|
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
|
||||||
|
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||||
|
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
|
||||||
|
import bou.amine.apps.readerforselfossv2.service.SearchService
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapter<VH>() {
|
abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapter<VH>() {
|
||||||
abstract var items: ArrayList<Item>
|
abstract var items: ArrayList<SelfossModel.Item>
|
||||||
abstract val api: SelfossApi
|
abstract val api: SelfossApi
|
||||||
|
abstract val apiDetailsService: ApiDetailsService
|
||||||
abstract val db: AppDatabase
|
abstract val db: AppDatabase
|
||||||
abstract val userIdentifier: String
|
abstract val userIdentifier: String
|
||||||
abstract val app: Activity
|
abstract val app: Activity
|
||||||
abstract val appColors: AppColors
|
abstract val appColors: AppColors
|
||||||
abstract val config: Config
|
abstract val config: Config
|
||||||
abstract val updateItems: (ArrayList<Item>) -> Unit
|
abstract val searchService: SearchService
|
||||||
|
abstract val updateItems: (ArrayList<SelfossModel.Item>) -> Unit
|
||||||
|
|
||||||
fun updateAllItems() {
|
fun updateAllItems() {
|
||||||
items = SharedItems.focusedItems
|
items = ArrayList() // TODO: SharedItems.focusedItems
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
updateItems(items)
|
updateItems(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun unmarkSnackbar(i: Item, position: Int) {
|
private fun unmarkSnackbar(i: SelfossModel.Item, position: Int) {
|
||||||
val s = Snackbar
|
val s = Snackbar
|
||||||
.make(
|
.make(
|
||||||
app.findViewById(R.id.coordLayout),
|
app.findViewById(R.id.coordLayout),
|
||||||
@ -37,12 +43,15 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
|
|||||||
Snackbar.LENGTH_LONG
|
Snackbar.LENGTH_LONG
|
||||||
)
|
)
|
||||||
.setAction(R.string.undo_string) {
|
.setAction(R.string.undo_string) {
|
||||||
SharedItems.unreadItem(app, api, db, i)
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
if (SharedItems.displayedItems == "unread") {
|
// Todo: SharedItems.unreadItem(app, api, db, i)
|
||||||
addItemAtIndex(i, position)
|
|
||||||
} else {
|
|
||||||
notifyItemChanged(position)
|
|
||||||
}
|
}
|
||||||
|
// Todo:
|
||||||
|
// if (SharedItems.displayedItems == "unread") {
|
||||||
|
// addItemAtIndex(i, position)
|
||||||
|
// } else {
|
||||||
|
// notifyItemChanged(position)
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
val view = s.view
|
val view = s.view
|
||||||
@ -59,14 +68,17 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
|
|||||||
Snackbar.LENGTH_LONG
|
Snackbar.LENGTH_LONG
|
||||||
)
|
)
|
||||||
.setAction(R.string.undo_string) {
|
.setAction(R.string.undo_string) {
|
||||||
SharedItems.readItem(app, api, db, items[position])
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
items = SharedItems.focusedItems
|
// Todo: SharedItems.readItem(app, api, db, items[position])
|
||||||
if (SharedItems.displayedItems == "unread") {
|
|
||||||
notifyItemRemoved(position)
|
|
||||||
updateItems(items)
|
|
||||||
} else {
|
|
||||||
notifyItemChanged(position)
|
|
||||||
}
|
}
|
||||||
|
// Todo: items = SharedItems.focusedItems
|
||||||
|
// Todo:
|
||||||
|
// if (SharedItems.displayedItems == "unread") {
|
||||||
|
// notifyItemRemoved(position)
|
||||||
|
// updateItems(items)
|
||||||
|
// } else {
|
||||||
|
// notifyItemChanged(position)
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
val view = s.view
|
val view = s.view
|
||||||
@ -76,40 +88,46 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun handleItemAtIndex(position: Int) {
|
fun handleItemAtIndex(position: Int) {
|
||||||
if (SharedItems.unreadItemStatusAtIndex(position)) {
|
// Todo:
|
||||||
readItemAtIndex(position)
|
// if (SharedItems.unreadItemStatusAtIndex(position)) {
|
||||||
} else {
|
// readItemAtIndex(position)
|
||||||
unreadItemAtIndex(position)
|
// } else {
|
||||||
}
|
// unreadItemAtIndex(position)
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun readItemAtIndex(position: Int) {
|
private fun readItemAtIndex(position: Int) {
|
||||||
val i = items[position]
|
val i = items[position]
|
||||||
SharedItems.readItem(app, api, db, i)
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
if (SharedItems.displayedItems == "unread") {
|
// Todo: SharedItems.readItem(app, api, db, i)
|
||||||
items.remove(i)
|
|
||||||
notifyItemRemoved(position)
|
|
||||||
updateItems(items)
|
|
||||||
} else {
|
|
||||||
notifyItemChanged(position)
|
|
||||||
}
|
}
|
||||||
|
// Todo:
|
||||||
|
// if (SharedItems.displayedItems == "unread") {
|
||||||
|
// items.remove(i)
|
||||||
|
// notifyItemRemoved(position)
|
||||||
|
// updateItems(items)
|
||||||
|
// } else {
|
||||||
|
// notifyItemChanged(position)
|
||||||
|
// }
|
||||||
unmarkSnackbar(i, position)
|
unmarkSnackbar(i, position)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun unreadItemAtIndex(position: Int) {
|
private fun unreadItemAtIndex(position: Int) {
|
||||||
SharedItems.unreadItem(app, api, db, items[position])
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
// Todo: SharedItems.unreadItem(app, api, db, items[position])
|
||||||
|
}
|
||||||
notifyItemChanged(position)
|
notifyItemChanged(position)
|
||||||
markSnackbar(position)
|
markSnackbar(position)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addItemAtIndex(item: Item, position: Int) {
|
fun addItemAtIndex(item: SelfossModel.Item, position: Int) {
|
||||||
items.add(position, item)
|
items.add(position, item)
|
||||||
notifyItemInserted(position)
|
notifyItemInserted(position)
|
||||||
updateItems(items)
|
updateItems(items)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addItemsAtEnd(newItems: List<Item>) {
|
fun addItemsAtEnd(newItems: List<SelfossModel.Item>) {
|
||||||
val oldSize = items.size
|
val oldSize = items.size
|
||||||
items.addAll(newItems)
|
items.addAll(newItems)
|
||||||
notifyItemRangeInserted(oldSize, newItems.size)
|
notifyItemRangeInserted(oldSize, newItems.size)
|
||||||
|
@ -2,31 +2,34 @@ package bou.amine.apps.readerforselfossv2.android.adapters
|
|||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import bou.amine.apps.readerforselfossv2.android.R
|
import bou.amine.apps.readerforselfossv2.android.R
|
||||||
import bou.amine.apps.readerforselfossv2.android.api.selfoss.SelfossApi
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.api.selfoss.Source
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.api.selfoss.SuccessResponse
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.databinding.SourceListItemBinding
|
import bou.amine.apps.readerforselfossv2.android.databinding.SourceListItemBinding
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.model.getIcon
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.model.getTitleDecoded
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularBitmapDrawable
|
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularBitmapDrawable
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAccessible
|
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.toTextDrawableString
|
import bou.amine.apps.readerforselfossv2.android.utils.toTextDrawableString
|
||||||
|
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
|
||||||
|
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||||
|
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
|
||||||
import com.amulyakhare.textdrawable.TextDrawable
|
import com.amulyakhare.textdrawable.TextDrawable
|
||||||
import com.amulyakhare.textdrawable.util.ColorGenerator
|
import com.amulyakhare.textdrawable.util.ColorGenerator
|
||||||
import retrofit2.Call
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import retrofit2.Callback
|
import kotlinx.coroutines.Dispatchers
|
||||||
import retrofit2.Response
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class SourcesListAdapter(
|
class SourcesListAdapter(
|
||||||
private val app: Activity,
|
private val app: Activity,
|
||||||
private val items: ArrayList<Source>,
|
private val items: ArrayList<SelfossModel.Source>,
|
||||||
private val api: SelfossApi
|
private val api: SelfossApi,
|
||||||
|
private val apiDetailsService: ApiDetailsService
|
||||||
) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>() {
|
) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>() {
|
||||||
private val c: Context = app.baseContext
|
private val c: Context = app.baseContext
|
||||||
private val generator: ColorGenerator = ColorGenerator.MATERIAL
|
private val generator: ColorGenerator = ColorGenerator.MATERIAL
|
||||||
@ -42,7 +45,7 @@ class SourcesListAdapter(
|
|||||||
val itm = items[position]
|
val itm = items[position]
|
||||||
config = Config(c)
|
config = Config(c)
|
||||||
|
|
||||||
if (itm.getIcon(c).isEmpty()) {
|
if (itm.getIcon(apiDetailsService.getBaseUrl()).isEmpty()) {
|
||||||
val color = generator.getColor(itm.getTitleDecoded())
|
val color = generator.getColor(itm.getTitleDecoded())
|
||||||
|
|
||||||
val drawable =
|
val drawable =
|
||||||
@ -52,7 +55,7 @@ class SourcesListAdapter(
|
|||||||
.build(itm.getTitleDecoded().toTextDrawableString(c), color)
|
.build(itm.getTitleDecoded().toTextDrawableString(c), color)
|
||||||
binding.itemImage.setImageDrawable(drawable)
|
binding.itemImage.setImageDrawable(drawable)
|
||||||
} else {
|
} else {
|
||||||
c.circularBitmapDrawable(config, itm.getIcon(c), binding.itemImage)
|
c.circularBitmapDrawable(config, itm.getIcon(apiDetailsService.getBaseUrl()), binding.itemImage)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.sourceTitle.text = itm.getTitleDecoded()
|
binding.sourceTitle.text = itm.getTitleDecoded()
|
||||||
@ -71,34 +74,22 @@ class SourcesListAdapter(
|
|||||||
val deleteBtn: Button = mView.findViewById(R.id.deleteBtn)
|
val deleteBtn: Button = mView.findViewById(R.id.deleteBtn)
|
||||||
|
|
||||||
deleteBtn.setOnClickListener {
|
deleteBtn.setOnClickListener {
|
||||||
if (c.isNetworkAccessible(null)) {
|
if (c.isNetworkAvailable(null)) {
|
||||||
val (id) = items[adapterPosition]
|
val (id) = items[adapterPosition]
|
||||||
api.deleteSource(id).enqueue(object : Callback<SuccessResponse> {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
override fun onResponse(
|
val action = api.deleteSource(id)
|
||||||
call: Call<SuccessResponse>,
|
if (action != null && action.isSuccess) {
|
||||||
response: Response<SuccessResponse>
|
items.removeAt(adapterPosition)
|
||||||
) {
|
notifyItemRemoved(adapterPosition)
|
||||||
if (response.body() != null && response.body()!!.isSuccess) {
|
notifyItemRangeChanged(adapterPosition, itemCount)
|
||||||
items.removeAt(adapterPosition)
|
} else {
|
||||||
notifyItemRemoved(adapterPosition)
|
|
||||||
notifyItemRangeChanged(adapterPosition, itemCount)
|
|
||||||
} else {
|
|
||||||
Toast.makeText(
|
|
||||||
app,
|
|
||||||
R.string.can_delete_source,
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
|
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
app,
|
app,
|
||||||
R.string.can_delete_source,
|
R.string.can_delete_source,
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.android.api.selfoss
|
|
||||||
|
|
||||||
import com.google.gson.JsonDeserializationContext
|
|
||||||
import com.google.gson.JsonDeserializer
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,245 +0,0 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.android.api.selfoss
|
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Context
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.SharedItems
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.getUnsafeHttpClient
|
|
||||||
import com.burgstaller.okhttp.AuthenticationCacheInterceptor
|
|
||||||
import com.burgstaller.okhttp.CachingAuthenticatorDecorator
|
|
||||||
import com.burgstaller.okhttp.DispatchingAuthenticator
|
|
||||||
import com.burgstaller.okhttp.basic.BasicAuthenticator
|
|
||||||
import com.burgstaller.okhttp.digest.CachingAuthenticator
|
|
||||||
import com.burgstaller.okhttp.digest.Credentials
|
|
||||||
import com.burgstaller.okhttp.digest.DigestAuthenticator
|
|
||||||
import com.google.gson.GsonBuilder
|
|
||||||
import okhttp3.*
|
|
||||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
|
||||||
import okhttp3.ResponseBody.Companion.toResponseBody
|
|
||||||
import okhttp3.logging.HttpLoggingInterceptor
|
|
||||||
import retrofit2.Call
|
|
||||||
import retrofit2.Retrofit
|
|
||||||
import retrofit2.converter.gson.GsonConverterFactory
|
|
||||||
import java.net.SocketTimeoutException
|
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
class SelfossApi(
|
|
||||||
c: Context,
|
|
||||||
callingActivity: Activity?,
|
|
||||||
isWithSelfSignedCert: Boolean,
|
|
||||||
timeout: Long
|
|
||||||
) {
|
|
||||||
|
|
||||||
private lateinit var service: SelfossService
|
|
||||||
private val config: Config = Config(c)
|
|
||||||
private val userName: String
|
|
||||||
private val password: String
|
|
||||||
|
|
||||||
fun OkHttpClient.Builder.maybeWithSelfSigned(isWithSelfSignedCert: Boolean): OkHttpClient.Builder =
|
|
||||||
if (isWithSelfSignedCert) {
|
|
||||||
getUnsafeHttpClient()
|
|
||||||
} else {
|
|
||||||
this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun OkHttpClient.Builder.maybeWithSettingsTimeout(timeout: Long): OkHttpClient.Builder =
|
|
||||||
if (timeout != -1L) {
|
|
||||||
this.readTimeout(timeout, TimeUnit.SECONDS)
|
|
||||||
.connectTimeout(timeout, TimeUnit.SECONDS)
|
|
||||||
} else {
|
|
||||||
this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Credentials.createAuthenticator(): DispatchingAuthenticator =
|
|
||||||
DispatchingAuthenticator.Builder()
|
|
||||||
.with("digest", DigestAuthenticator(this))
|
|
||||||
.with("basic", BasicAuthenticator(this))
|
|
||||||
.build()
|
|
||||||
|
|
||||||
fun DispatchingAuthenticator.getHttpClien(isWithSelfSignedCert: Boolean, timeout: Long): OkHttpClient.Builder {
|
|
||||||
val authCache = ConcurrentHashMap<String, CachingAuthenticator>()
|
|
||||||
return OkHttpClient
|
|
||||||
.Builder()
|
|
||||||
.maybeWithSettingsTimeout(timeout)
|
|
||||||
.maybeWithSelfSigned(isWithSelfSignedCert)
|
|
||||||
.authenticator(CachingAuthenticatorDecorator(this, authCache))
|
|
||||||
.addInterceptor(AuthenticationCacheInterceptor(authCache))
|
|
||||||
.addInterceptor(object: Interceptor {
|
|
||||||
override fun intercept(chain: Interceptor.Chain): Response {
|
|
||||||
val request: Request = chain.request()
|
|
||||||
val response: Response = chain.proceed(request)
|
|
||||||
|
|
||||||
if (response.code == 408) {
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
userName = config.userLogin
|
|
||||||
password = config.userPassword
|
|
||||||
|
|
||||||
val authenticator =
|
|
||||||
Credentials(
|
|
||||||
config.httpUserLogin,
|
|
||||||
config.httpUserPassword
|
|
||||||
).createAuthenticator()
|
|
||||||
|
|
||||||
val gson =
|
|
||||||
GsonBuilder()
|
|
||||||
.registerTypeAdapter(Boolean::class.javaPrimitiveType, BooleanTypeAdapter())
|
|
||||||
.registerTypeAdapter(SelfossTagType::class.java, SelfossTagTypeTypeAdapter())
|
|
||||||
.setLenient()
|
|
||||||
.create()
|
|
||||||
|
|
||||||
val logging = HttpLoggingInterceptor()
|
|
||||||
|
|
||||||
|
|
||||||
logging.level = HttpLoggingInterceptor.Level.NONE
|
|
||||||
val httpClient = authenticator.getHttpClien(isWithSelfSignedCert, timeout)
|
|
||||||
|
|
||||||
val timeoutCode = 504
|
|
||||||
httpClient
|
|
||||||
.addInterceptor { chain ->
|
|
||||||
val res = chain.proceed(chain.request())
|
|
||||||
if (res.code == timeoutCode) {
|
|
||||||
throw SocketTimeoutException("timeout")
|
|
||||||
}
|
|
||||||
res
|
|
||||||
}
|
|
||||||
.addInterceptor(logging)
|
|
||||||
.addInterceptor { chain ->
|
|
||||||
val request = chain.request()
|
|
||||||
try {
|
|
||||||
chain.proceed(request)
|
|
||||||
} catch (e: SocketTimeoutException) {
|
|
||||||
Response.Builder()
|
|
||||||
.code(timeoutCode)
|
|
||||||
.protocol(Protocol.HTTP_2)
|
|
||||||
.body("".toResponseBody("text/plain".toMediaTypeOrNull()))
|
|
||||||
.message("")
|
|
||||||
.request(request)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
val retrofit =
|
|
||||||
Retrofit
|
|
||||||
.Builder()
|
|
||||||
.baseUrl(config.baseUrl)
|
|
||||||
.client(httpClient.build())
|
|
||||||
.addConverterFactory(GsonConverterFactory.create(gson))
|
|
||||||
.build()
|
|
||||||
service = retrofit.create(SelfossService::class.java)
|
|
||||||
} catch (e: IllegalArgumentException) {
|
|
||||||
if (callingActivity != null) {
|
|
||||||
Config.logoutAndRedirect(c, callingActivity, config.settings.edit(), baseUrlFail = true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun login(): Call<SuccessResponse> =
|
|
||||||
service.loginToSelfoss(config.userLogin, config.userPassword)
|
|
||||||
|
|
||||||
suspend fun readItems(
|
|
||||||
itemsNumber: Int,
|
|
||||||
offset: Int
|
|
||||||
): retrofit2.Response<List<Item>> =
|
|
||||||
getItems("read", SharedItems.tagFilter, SharedItems.sourceIDFilter, SharedItems.searchFilter, itemsNumber, offset)
|
|
||||||
|
|
||||||
suspend fun newItems(
|
|
||||||
itemsNumber: Int,
|
|
||||||
offset: Int
|
|
||||||
): retrofit2.Response<List<Item>> =
|
|
||||||
getItems("unread", SharedItems.tagFilter, SharedItems.sourceIDFilter, SharedItems.searchFilter, itemsNumber, offset)
|
|
||||||
|
|
||||||
suspend fun starredItems(
|
|
||||||
itemsNumber: Int,
|
|
||||||
offset: Int
|
|
||||||
): retrofit2.Response<List<Item>> =
|
|
||||||
getItems("starred", SharedItems.tagFilter, SharedItems.sourceIDFilter, SharedItems.searchFilter, itemsNumber, offset)
|
|
||||||
|
|
||||||
fun allItems(): Call<List<Item>> =
|
|
||||||
service.allItems(userName, password)
|
|
||||||
|
|
||||||
suspend fun allNewItems(): retrofit2.Response<List<Item>> =
|
|
||||||
getItems("unread", null, null, null, 200, 0)
|
|
||||||
|
|
||||||
suspend fun allReadItems(): retrofit2.Response<List<Item>> =
|
|
||||||
getItems("read", null, null, null, 200, 0)
|
|
||||||
|
|
||||||
suspend fun allStarredItems(): retrofit2.Response<List<Item>> =
|
|
||||||
getItems("read", null, null, null, 200, 0)
|
|
||||||
|
|
||||||
private suspend fun getItems(
|
|
||||||
type: String,
|
|
||||||
tag: String?,
|
|
||||||
sourceId: Long?,
|
|
||||||
search: String?,
|
|
||||||
items: Int,
|
|
||||||
offset: Int
|
|
||||||
): retrofit2.Response<List<Item>> =
|
|
||||||
service.getItems(type, tag, sourceId, search, null, userName, password, items, offset)
|
|
||||||
|
|
||||||
suspend fun updateItems(
|
|
||||||
updatedSince: String
|
|
||||||
): retrofit2.Response<List<Item>> =
|
|
||||||
service.getItems("read", null, null, null, updatedSince, userName, password, 200, 0)
|
|
||||||
|
|
||||||
fun markItem(itemId: String): Call<SuccessResponse> =
|
|
||||||
service.markAsRead(itemId, userName, password)
|
|
||||||
|
|
||||||
fun unmarkItem(itemId: String): Call<SuccessResponse> =
|
|
||||||
service.unmarkAsRead(itemId, userName, password)
|
|
||||||
|
|
||||||
suspend fun readAll(ids: List<String>): SuccessResponse =
|
|
||||||
service.markAllAsRead(ids, userName, password)
|
|
||||||
|
|
||||||
fun starrItem(itemId: String): Call<SuccessResponse> =
|
|
||||||
service.starr(itemId, userName, password)
|
|
||||||
|
|
||||||
fun unstarrItem(itemId: String): Call<SuccessResponse> =
|
|
||||||
service.unstarr(itemId, userName, password)
|
|
||||||
|
|
||||||
suspend fun stats(): retrofit2.Response<Stats> = service.stats(userName, password)
|
|
||||||
|
|
||||||
val tags: Call<List<Tag>>
|
|
||||||
get() = service.tags(userName, password)
|
|
||||||
|
|
||||||
fun update(): Call<String> =
|
|
||||||
service.update(userName, password)
|
|
||||||
|
|
||||||
val apiVersion: Call<ApiVersion>
|
|
||||||
get() = service.version()
|
|
||||||
|
|
||||||
val sources: Call<List<Source>>
|
|
||||||
get() = service.sources(userName, password)
|
|
||||||
|
|
||||||
fun deleteSource(id: String): Call<SuccessResponse> =
|
|
||||||
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)
|
|
||||||
|
|
||||||
fun createSourceApi2(
|
|
||||||
title: String,
|
|
||||||
url: String,
|
|
||||||
spout: String,
|
|
||||||
tags: List<String>,
|
|
||||||
filter: String
|
|
||||||
): Call<SuccessResponse> =
|
|
||||||
service.createSourceApi2(title, url, spout, tags, filter, userName, password)
|
|
||||||
}
|
|
@ -1,134 +0,0 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.android.api.selfoss
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.SharedItems
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable
|
|
||||||
import kotlinx.coroutines.*
|
|
||||||
import retrofit2.Response
|
|
||||||
|
|
||||||
suspend fun getAndStoreAllItems(context: Context, api: SelfossApi, db: AppDatabase) = withContext(Dispatchers.IO) {
|
|
||||||
if (isNetworkAvailable(context)) {
|
|
||||||
launch {
|
|
||||||
try {
|
|
||||||
enqueueArticles(api.allNewItems(), db, true)
|
|
||||||
} catch (e: Throwable) {}
|
|
||||||
}
|
|
||||||
launch {
|
|
||||||
try {
|
|
||||||
enqueueArticles(api.allReadItems(), db, false)
|
|
||||||
} catch (e: Throwable) {}
|
|
||||||
}
|
|
||||||
launch {
|
|
||||||
try {
|
|
||||||
enqueueArticles(api.allStarredItems(), db, false)
|
|
||||||
} catch (e: Throwable) {}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
launch { SharedItems.updateDatabase(db) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun updateItems(context: Context, api: SelfossApi, db: AppDatabase) = coroutineScope {
|
|
||||||
if (isNetworkAvailable(context)) {
|
|
||||||
launch {
|
|
||||||
try {
|
|
||||||
enqueueArticles(api.updateItems(SharedItems.items[0].datetime), db, true)
|
|
||||||
} catch (e: Throwable) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun refreshFocusedItems(context: Context, api: SelfossApi, db: AppDatabase, itemsNumber: Int) = withContext(Dispatchers.IO) {
|
|
||||||
if (isNetworkAvailable(context)) {
|
|
||||||
val response = when (SharedItems.displayedItems) {
|
|
||||||
"read" -> api.readItems(itemsNumber, 0)
|
|
||||||
"unread" -> api.newItems(itemsNumber, 0)
|
|
||||||
"starred" -> api.starredItems(itemsNumber, 0)
|
|
||||||
else -> api.readItems(itemsNumber, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.isSuccessful) {
|
|
||||||
SharedItems.refreshFocusedItems(response.body() as ArrayList<Item>)
|
|
||||||
SharedItems.updateDatabase(db)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getReadItems(context: Context, api: SelfossApi, db: AppDatabase, itemsNumber: Int, offset: Int) = withContext(Dispatchers.IO) {
|
|
||||||
if (isNetworkAvailable(context)) {
|
|
||||||
try {
|
|
||||||
enqueueArticles(api.readItems( itemsNumber, offset), db, false)
|
|
||||||
SharedItems.fetchedAll = true
|
|
||||||
SharedItems.updateDatabase(db)
|
|
||||||
} catch (e: Throwable) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getUnreadItems(context: Context, api: SelfossApi, db: AppDatabase, itemsNumber: Int, offset: Int) = withContext(Dispatchers.IO) {
|
|
||||||
if (isNetworkAvailable(context)) {
|
|
||||||
try {
|
|
||||||
if (!SharedItems.fetchedUnread) {
|
|
||||||
SharedItems.clearDBItems(db)
|
|
||||||
}
|
|
||||||
enqueueArticles(api.newItems(itemsNumber, offset), db, false)
|
|
||||||
SharedItems.fetchedUnread = true
|
|
||||||
} catch (e: Throwable) {}
|
|
||||||
}
|
|
||||||
SharedItems.updateDatabase(db)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getStarredItems(context: Context, api: SelfossApi, db: AppDatabase, itemsNumber: Int, offset: Int) = withContext(Dispatchers.IO) {
|
|
||||||
if (isNetworkAvailable(context)) {
|
|
||||||
try {
|
|
||||||
enqueueArticles(api.starredItems(itemsNumber, offset), db, false)
|
|
||||||
SharedItems.fetchedStarred = true
|
|
||||||
SharedItems.updateDatabase(db)
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun readAll(context: Context, api: SelfossApi, db: AppDatabase): Boolean {
|
|
||||||
var success = false
|
|
||||||
if (isNetworkAvailable(context)) {
|
|
||||||
try {
|
|
||||||
val ids = SharedItems.focusedItems.map { it.id }
|
|
||||||
if (ids.isNotEmpty()) {
|
|
||||||
val result = api.readAll(ids)
|
|
||||||
SharedItems.readItems(db, ids)
|
|
||||||
success = result.isSuccess
|
|
||||||
}
|
|
||||||
} catch (e: Throwable) {}
|
|
||||||
}
|
|
||||||
return success
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun reloadBadges(context: Context, api: SelfossApi) = withContext(Dispatchers.IO) {
|
|
||||||
if (isNetworkAvailable(context)) {
|
|
||||||
try {
|
|
||||||
val response = api.stats()
|
|
||||||
|
|
||||||
if (response.isSuccessful) {
|
|
||||||
val badges = response.body()
|
|
||||||
SharedItems.badgeUnread = badges!!.unread
|
|
||||||
SharedItems.badgeAll = badges.total
|
|
||||||
SharedItems.badgeStarred = badges.starred
|
|
||||||
}
|
|
||||||
} catch (e: Throwable) {}
|
|
||||||
} else {
|
|
||||||
SharedItems.computeBadges()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun enqueueArticles(response: Response<List<Item>>, db: AppDatabase, clearDatabase: Boolean) {
|
|
||||||
if (response.isSuccessful) {
|
|
||||||
if (clearDatabase) {
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
SharedItems.clearDBItems(db)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val allItems = response.body() as ArrayList<Item>
|
|
||||||
SharedItems.appendNewItems(allItems)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,253 +0,0 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.android.api.selfoss
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Parcel
|
|
||||||
import android.os.Parcelable
|
|
||||||
import android.text.Html
|
|
||||||
import android.webkit.URLUtil
|
|
||||||
import org.jsoup.Jsoup
|
|
||||||
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.isEmptyOrNullOrNullString
|
|
||||||
import com.bumptech.glide.Glide
|
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
|
||||||
import com.bumptech.glide.request.RequestOptions
|
|
||||||
import com.google.gson.annotations.SerializedName
|
|
||||||
import java.util.*
|
|
||||||
import kotlin.collections.ArrayList
|
|
||||||
|
|
||||||
private fun constructUrl(config: Config?, path: String, file: String?): String {
|
|
||||||
return if (file.isEmptyOrNullOrNullString()) {
|
|
||||||
""
|
|
||||||
} else {
|
|
||||||
val baseUriBuilder = Uri.parse(config!!.baseUrl).buildUpon()
|
|
||||||
baseUriBuilder.appendPath(path).appendPath(file)
|
|
||||||
|
|
||||||
baseUriBuilder.toString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class Tag(
|
|
||||||
@SerializedName("tag") val tag: String,
|
|
||||||
@SerializedName("color") val color: String,
|
|
||||||
@SerializedName("unread") val unread: Int
|
|
||||||
) {
|
|
||||||
fun getTitleDecoded(): String {
|
|
||||||
return Html.fromHtml(tag).toString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
data class Spout(
|
|
||||||
@SerializedName("name") val name: String,
|
|
||||||
@SerializedName("description") val description: String
|
|
||||||
)
|
|
||||||
|
|
||||||
data class ApiVersion(
|
|
||||||
@SerializedName("version") val version: String?,
|
|
||||||
@SerializedName("apiversion") val apiversion: String?
|
|
||||||
) {
|
|
||||||
fun getApiMajorVersion() : Int {
|
|
||||||
var versionNumber = 0
|
|
||||||
if (apiversion != null) {
|
|
||||||
versionNumber = apiversion.substringBefore(".").toInt()
|
|
||||||
}
|
|
||||||
return versionNumber
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class Source(
|
|
||||||
@SerializedName("id") val id: String,
|
|
||||||
@SerializedName("title") val title: String,
|
|
||||||
@SerializedName("tags") val tags: SelfossTagType,
|
|
||||||
@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)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getTitleDecoded(): String {
|
|
||||||
return Html.fromHtml(title).toString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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") var unread: Boolean,
|
|
||||||
@SerializedName("starred") var starred: Boolean,
|
|
||||||
@SerializedName("thumbnail") val thumbnail: String?,
|
|
||||||
@SerializedName("icon") val icon: String?,
|
|
||||||
@SerializedName("link") val link: String,
|
|
||||||
@SerializedName("sourcetitle") val sourcetitle: String,
|
|
||||||
@SerializedName("tags") val tags: SelfossTagType
|
|
||||||
) : Parcelable {
|
|
||||||
|
|
||||||
var config: Config? = null
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
@JvmField val CREATOR: Parcelable.Creator<Item> = object : Parcelable.Creator<Item> {
|
|
||||||
override fun createFromParcel(source: Parcel): Item = Item(source)
|
|
||||||
override fun newArray(size: Int): Array<Item?> = arrayOfNulls(size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(source: Parcel) : this(
|
|
||||||
id = source.readString().orEmpty(),
|
|
||||||
datetime = source.readString().orEmpty(),
|
|
||||||
title = source.readString().orEmpty(),
|
|
||||||
content = source.readString().orEmpty(),
|
|
||||||
unread = 0.toByte() != source.readByte(),
|
|
||||||
starred = 0.toByte() != source.readByte(),
|
|
||||||
thumbnail = source.readString(),
|
|
||||||
icon = source.readString(),
|
|
||||||
link = source.readString().orEmpty(),
|
|
||||||
sourcetitle = source.readString().orEmpty(),
|
|
||||||
tags = if (source.readParcelable<SelfossTagType>(ClassLoader.getSystemClassLoader()) != null) source.readParcelable(ClassLoader.getSystemClassLoader())!! else SelfossTagType("")
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun describeContents() = 0
|
|
||||||
|
|
||||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
|
||||||
dest.writeString(id)
|
|
||||||
dest.writeString(datetime)
|
|
||||||
dest.writeString(title)
|
|
||||||
dest.writeString(content)
|
|
||||||
dest.writeByte((if (unread) 1 else 0))
|
|
||||||
dest.writeByte((if (starred) 1 else 0))
|
|
||||||
dest.writeString(thumbnail)
|
|
||||||
dest.writeString(icon)
|
|
||||||
dest.writeString(link)
|
|
||||||
dest.writeString(sourcetitle)
|
|
||||||
dest.writeParcelable(tags, flags)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getIcon(app: Context): String {
|
|
||||||
if (config == null) {
|
|
||||||
config = Config(app)
|
|
||||||
}
|
|
||||||
return constructUrl(config, "favicons", icon)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getThumbnail(app: Context): String {
|
|
||||||
if (config == null) {
|
|
||||||
config = Config(app)
|
|
||||||
}
|
|
||||||
return constructUrl(config, "thumbnails", thumbnail)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getImages() : ArrayList<String> {
|
|
||||||
val allImages = ArrayList<String>()
|
|
||||||
|
|
||||||
for ( image in Jsoup.parse(content).getElementsByTag("img")) {
|
|
||||||
val url = image.attr("src")
|
|
||||||
if (url.lowercase(Locale.US).contains(".jpg") ||
|
|
||||||
url.lowercase(Locale.US).contains(".jpeg") ||
|
|
||||||
url.lowercase(Locale.US).contains(".png") ||
|
|
||||||
url.lowercase(Locale.US).contains(".webp"))
|
|
||||||
{
|
|
||||||
allImages.add(url)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return allImages
|
|
||||||
}
|
|
||||||
|
|
||||||
fun preloadImages(context: Context) : Boolean {
|
|
||||||
val imageUrls = this.getImages()
|
|
||||||
|
|
||||||
val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL).timeout(10000)
|
|
||||||
|
|
||||||
|
|
||||||
try {
|
|
||||||
for (url in imageUrls) {
|
|
||||||
if ( URLUtil.isValidUrl(url)) {
|
|
||||||
val image = Glide.with(context).asBitmap()
|
|
||||||
.apply(glideOptions)
|
|
||||||
.load(url).submit()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e : Error) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getTitleDecoded(): String {
|
|
||||||
return Html.fromHtml(title).toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getSourceTitle(): String {
|
|
||||||
return Html.fromHtml(sourcetitle).toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: maybe find a better way to handle these kind of urls
|
|
||||||
fun getLinkDecoded(): String {
|
|
||||||
var stringUrl: String
|
|
||||||
stringUrl =
|
|
||||||
if (link.startsWith("http://news.google.com/news/") || link.startsWith("https://news.google.com/news/")) {
|
|
||||||
if (link.contains("&url=")) {
|
|
||||||
link.substringAfter("&url=")
|
|
||||||
} else {
|
|
||||||
this.link.replace("&", "&")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.link.replace("&", "&")
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle :443 => https
|
|
||||||
if (stringUrl.contains(":443")) {
|
|
||||||
stringUrl = stringUrl.replace(":443", "").replace("http://", "https://")
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle url not starting with http
|
|
||||||
if (stringUrl.startsWith("//")) {
|
|
||||||
stringUrl = "http:$stringUrl"
|
|
||||||
}
|
|
||||||
|
|
||||||
return stringUrl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class SelfossTagType(val tags: String) : Parcelable {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
@JvmField val CREATOR: Parcelable.Creator<SelfossTagType> =
|
|
||||||
object : Parcelable.Creator<SelfossTagType> {
|
|
||||||
override fun createFromParcel(source: Parcel): SelfossTagType =
|
|
||||||
SelfossTagType(source)
|
|
||||||
|
|
||||||
override fun newArray(size: Int): Array<SelfossTagType?> = arrayOfNulls(size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(source: Parcel) : this(
|
|
||||||
tags = source.readString().orEmpty()
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun describeContents() = 0
|
|
||||||
|
|
||||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
|
||||||
dest.writeString(tags)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,141 +0,0 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.android.api.selfoss
|
|
||||||
|
|
||||||
import retrofit2.Call
|
|
||||||
import retrofit2.Response
|
|
||||||
import retrofit2.http.DELETE
|
|
||||||
import retrofit2.http.Field
|
|
||||||
import retrofit2.http.FormUrlEncoded
|
|
||||||
import retrofit2.http.GET
|
|
||||||
import retrofit2.http.Headers
|
|
||||||
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")
|
|
||||||
suspend fun getItems(
|
|
||||||
@Query("type") type: String,
|
|
||||||
@Query("tag") tag: String?,
|
|
||||||
@Query("source") source: Long?,
|
|
||||||
@Query("search") search: String?,
|
|
||||||
@Query("updatedsince") updatedSince: String?,
|
|
||||||
@Query("username") username: String,
|
|
||||||
@Query("password") password: String,
|
|
||||||
@Query("items") items: Int,
|
|
||||||
@Query("offset") offset: Int
|
|
||||||
): Response<List<Item>>
|
|
||||||
|
|
||||||
@GET("items")
|
|
||||||
fun allItems(
|
|
||||||
@Query("username") username: String,
|
|
||||||
@Query("password") password: String
|
|
||||||
): 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>
|
|
||||||
|
|
||||||
@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>
|
|
||||||
|
|
||||||
@FormUrlEncoded
|
|
||||||
@POST("mark")
|
|
||||||
suspend fun markAllAsRead(
|
|
||||||
@Field("ids[]") ids: List<String>,
|
|
||||||
@Query("username") username: String,
|
|
||||||
@Query("password") password: String
|
|
||||||
): 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>
|
|
||||||
|
|
||||||
@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>
|
|
||||||
|
|
||||||
@GET("stats")
|
|
||||||
suspend fun stats(
|
|
||||||
@Query("username") username: String,
|
|
||||||
@Query("password") password: String
|
|
||||||
): Response<Stats>
|
|
||||||
|
|
||||||
@GET("tags")
|
|
||||||
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>
|
|
||||||
|
|
||||||
@GET("sources/spouts")
|
|
||||||
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<Source>>
|
|
||||||
|
|
||||||
@GET("api/about")
|
|
||||||
fun version(): Call<ApiVersion>
|
|
||||||
|
|
||||||
@DELETE("source/{id}")
|
|
||||||
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>
|
|
||||||
|
|
||||||
@FormUrlEncoded
|
|
||||||
@POST("source")
|
|
||||||
fun createSourceApi2(
|
|
||||||
@Field("title") title: String,
|
|
||||||
@Field("url") url: String,
|
|
||||||
@Field("spout") spout: String,
|
|
||||||
@Field("tags[]") tags: List<String>,
|
|
||||||
@Field("filter") filter: String,
|
|
||||||
@Query("username") username: String,
|
|
||||||
@Query("password") password: String
|
|
||||||
): Call<SuccessResponse>
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.android.api.selfoss
|
|
||||||
|
|
||||||
import com.google.gson.JsonDeserializationContext
|
|
||||||
import com.google.gson.JsonDeserializer
|
|
||||||
import com.google.gson.JsonElement
|
|
||||||
import com.google.gson.JsonParseException
|
|
||||||
import java.lang.reflect.Type
|
|
||||||
|
|
||||||
internal class SelfossTagTypeTypeAdapter : JsonDeserializer<SelfossTagType> {
|
|
||||||
|
|
||||||
@Throws(JsonParseException::class)
|
|
||||||
override fun deserialize(
|
|
||||||
json: JsonElement,
|
|
||||||
typeOfT: Type,
|
|
||||||
context: JsonDeserializationContext
|
|
||||||
): SelfossTagType? =
|
|
||||||
if (json.isJsonArray) {
|
|
||||||
SelfossTagType(json.asJsonArray.joinToString(",") { it.toString() })
|
|
||||||
} else {
|
|
||||||
SelfossTagType(json.toString())
|
|
||||||
}
|
|
||||||
}
|
|
@ -14,22 +14,27 @@ import androidx.work.Worker
|
|||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import bou.amine.apps.readerforselfossv2.android.MainActivity
|
import bou.amine.apps.readerforselfossv2.android.MainActivity
|
||||||
import bou.amine.apps.readerforselfossv2.android.R
|
import bou.amine.apps.readerforselfossv2.android.R
|
||||||
import bou.amine.apps.readerforselfossv2.android.api.selfoss.SelfossApi
|
import bou.amine.apps.readerforselfossv2.android.model.preloadImages
|
||||||
import bou.amine.apps.readerforselfossv2.android.api.selfoss.getAndStoreAllItems
|
import bou.amine.apps.readerforselfossv2.android.persistence.AndroidDeviceDatabase
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.persistence.AndroidDeviceDatabaseService
|
||||||
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
|
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
|
||||||
import bou.amine.apps.readerforselfossv2.android.persistence.entities.ActionEntity
|
import bou.amine.apps.readerforselfossv2.android.persistence.entities.ActionEntity
|
||||||
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_1_2
|
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_1_2
|
||||||
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_2_3
|
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_2_3
|
||||||
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_3_4
|
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_3_4
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.service.AndroidApiDetailsService
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.SharedItems
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable
|
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable
|
||||||
|
|
||||||
|
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
|
||||||
|
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||||
|
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
|
||||||
|
import bou.amine.apps.readerforselfossv2.service.SearchService
|
||||||
|
import bou.amine.apps.readerforselfossv2.service.SelfossService
|
||||||
|
import bou.amine.apps.readerforselfossv2.utils.DateUtils
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import retrofit2.Call
|
|
||||||
import retrofit2.Callback
|
|
||||||
import retrofit2.Response
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.concurrent.schedule
|
import kotlin.concurrent.schedule
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
@ -43,14 +48,20 @@ override fun doWork(): Result {
|
|||||||
val sharedPref = PreferenceManager.getDefaultSharedPreferences(this.context)
|
val sharedPref = PreferenceManager.getDefaultSharedPreferences(this.context)
|
||||||
val periodicRefresh = sharedPref.getBoolean("periodic_refresh", false)
|
val periodicRefresh = sharedPref.getBoolean("periodic_refresh", false)
|
||||||
if (periodicRefresh) {
|
if (periodicRefresh) {
|
||||||
|
val apiDetailsService = AndroidApiDetailsService(this.context)
|
||||||
val api = SelfossApi(
|
val api = SelfossApi(
|
||||||
this.context,
|
// this.context,
|
||||||
null,
|
// null,
|
||||||
settings.getBoolean("isSelfSignedCert", false),
|
// settings.getBoolean("isSelfSignedCert", false),
|
||||||
sharedPref.getString("api_timeout", "-1")!!.toLong()
|
// sharedPref.getString("api_timeout", "-1")!!.toLong()
|
||||||
|
apiDetailsService
|
||||||
)
|
)
|
||||||
|
|
||||||
if (isNetworkAvailable(context)) {
|
val dateUtils = DateUtils(apiDetailsService)
|
||||||
|
val searchService = SearchService(dateUtils)
|
||||||
|
val service = SelfossService(api, AndroidDeviceDatabaseService(AndroidDeviceDatabase(applicationContext), searchService), searchService)
|
||||||
|
|
||||||
|
if (context.isNetworkAvailable()) {
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
val notificationManager =
|
val notificationManager =
|
||||||
@ -80,26 +91,26 @@ override fun doWork(): Result {
|
|||||||
actions.forEach { action ->
|
actions.forEach { action ->
|
||||||
when {
|
when {
|
||||||
action.read -> doAndReportOnFail(
|
action.read -> doAndReportOnFail(
|
||||||
api.markItem(action.articleId),
|
api.markAsRead(action.articleId),
|
||||||
action
|
action
|
||||||
)
|
)
|
||||||
action.unread -> doAndReportOnFail(
|
action.unread -> doAndReportOnFail(
|
||||||
api.unmarkItem(action.articleId),
|
api.unmarkAsRead(action.articleId),
|
||||||
action
|
action
|
||||||
)
|
)
|
||||||
action.starred -> doAndReportOnFail(
|
action.starred -> doAndReportOnFail(
|
||||||
api.starrItem(action.articleId),
|
api.starr(action.articleId),
|
||||||
action
|
action
|
||||||
)
|
)
|
||||||
action.unstarred -> doAndReportOnFail(
|
action.unstarred -> doAndReportOnFail(
|
||||||
api.unstarrItem(action.articleId),
|
api.unstarr(action.articleId),
|
||||||
action
|
action
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getAndStoreAllItems(context, api, db)
|
service.getAndStoreAllItems(context.isNetworkAvailable())
|
||||||
SharedItems.updateDatabase(db)
|
// TODO: SharedItems.updateDatabase(db, dateUtils)
|
||||||
storeItems(notifyNewItems, notificationManager)
|
storeItems(notifyNewItems, notificationManager)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,10 +120,10 @@ override fun doWork(): Result {
|
|||||||
|
|
||||||
private fun storeItems(notifyNewItems: Boolean, notificationManager: NotificationManager) {
|
private fun storeItems(notifyNewItems: Boolean, notificationManager: NotificationManager) {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
val apiItems = SharedItems.items
|
val apiItems = emptyList<SelfossModel.Item>() // TODO: SharedItems.items
|
||||||
|
|
||||||
|
|
||||||
val newSize = apiItems.filter { it.unread }.size
|
val newSize = apiItems.filter { it.unread == 1 }.size
|
||||||
if (notifyNewItems && newSize > 0) {
|
if (notifyNewItems && newSize > 0) {
|
||||||
|
|
||||||
val intent = Intent(context, MainActivity::class.java).apply {
|
val intent = Intent(context, MainActivity::class.java).apply {
|
||||||
@ -151,19 +162,11 @@ override fun doWork(): Result {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <T> doAndReportOnFail(call: Call<T>, action: ActionEntity) {
|
private fun doAndReportOnFail(result: SelfossModel.SuccessResponse?, action: ActionEntity) {
|
||||||
call.enqueue(object : Callback<T> {
|
if (result != null && result.isSuccess) {
|
||||||
override fun onResponse(
|
thread {
|
||||||
call: Call<T>,
|
db.actionsDao().delete(action)
|
||||||
response: Response<T>
|
|
||||||
) {
|
|
||||||
thread {
|
|
||||||
db.actionsDao().delete(action)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
override fun onFailure(call: Call<T>, t: Throwable) {
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -10,39 +10,51 @@ import android.graphics.Typeface
|
|||||||
import android.graphics.drawable.ColorDrawable
|
import android.graphics.drawable.ColorDrawable
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.preference.PreferenceManager
|
|
||||||
import android.view.*
|
import android.view.*
|
||||||
import android.webkit.*
|
import android.webkit.*
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.core.widget.NestedScrollView
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.browser.customtabs.CustomTabsIntent
|
import androidx.browser.customtabs.CustomTabsIntent
|
||||||
import androidx.core.content.res.ResourcesCompat
|
import androidx.core.content.res.ResourcesCompat
|
||||||
|
import androidx.core.widget.NestedScrollView
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.room.Room
|
import androidx.room.Room
|
||||||
import bou.amine.apps.readerforselfossv2.android.ImageActivity
|
import bou.amine.apps.readerforselfossv2.android.ImageActivity
|
||||||
import bou.amine.apps.readerforselfossv2.android.R
|
import bou.amine.apps.readerforselfossv2.android.R
|
||||||
import bou.amine.apps.readerforselfossv2.android.api.mercury.MercuryApi
|
import bou.amine.apps.readerforselfossv2.android.api.mercury.MercuryApi
|
||||||
import bou.amine.apps.readerforselfossv2.android.api.mercury.ParsedContent
|
import bou.amine.apps.readerforselfossv2.android.api.mercury.ParsedContent
|
||||||
import bou.amine.apps.readerforselfossv2.android.api.selfoss.Item
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.api.selfoss.SelfossApi
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.databinding.FragmentArticleBinding
|
import bou.amine.apps.readerforselfossv2.android.databinding.FragmentArticleBinding
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.model.*
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.persistence.AndroidDeviceDatabase
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.persistence.AndroidDeviceDatabaseService
|
||||||
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
|
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.persistence.entities.AndroidItemEntity
|
||||||
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_1_2
|
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_1_2
|
||||||
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_2_3
|
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_2_3
|
||||||
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_3_4
|
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_3_4
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.service.AndroidApiDetailsService
|
||||||
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
|
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.*
|
import bou.amine.apps.readerforselfossv2.android.utils.*
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper
|
import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.loadMaybeBasicAuth
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.getBitmapInputStream
|
import bou.amine.apps.readerforselfossv2.android.utils.glide.getBitmapInputStream
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAccessible
|
import bou.amine.apps.readerforselfossv2.android.utils.glide.loadMaybeBasicAuth
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.*
|
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable
|
||||||
|
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
|
||||||
|
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||||
|
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
|
||||||
|
import bou.amine.apps.readerforselfossv2.service.SearchService
|
||||||
|
import bou.amine.apps.readerforselfossv2.service.SelfossService
|
||||||
|
import bou.amine.apps.readerforselfossv2.utils.DateUtils
|
||||||
|
import bou.amine.apps.readerforselfossv2.utils.isEmptyOrNullOrNullString
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import com.bumptech.glide.request.RequestOptions
|
import com.bumptech.glide.request.RequestOptions
|
||||||
import com.github.rubensousa.floatingtoolbar.FloatingToolbar
|
import com.github.rubensousa.floatingtoolbar.FloatingToolbar
|
||||||
|
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
import retrofit2.Callback
|
import retrofit2.Callback
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
@ -50,11 +62,13 @@ import java.net.MalformedURLException
|
|||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.ExecutionException
|
import java.util.concurrent.ExecutionException
|
||||||
import kotlin.collections.ArrayList
|
|
||||||
|
|
||||||
class ArticleFragment : Fragment() {
|
class ArticleFragment : Fragment() {
|
||||||
|
private lateinit var dbService: AndroidDeviceDatabaseService
|
||||||
|
private lateinit var apiDetailsService: ApiDetailsService
|
||||||
|
private lateinit var service: SelfossService<AndroidItemEntity>
|
||||||
private var fontSize: Int = 16
|
private var fontSize: Int = 16
|
||||||
private lateinit var item: Item
|
private lateinit var item: SelfossModel.Item
|
||||||
private var mCustomTabActivityHelper: CustomTabActivityHelper? = null
|
private var mCustomTabActivityHelper: CustomTabActivityHelper? = null
|
||||||
private lateinit var url: String
|
private lateinit var url: String
|
||||||
private lateinit var contentText: String
|
private lateinit var contentText: String
|
||||||
@ -91,6 +105,12 @@ class ArticleFragment : Fragment() {
|
|||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
apiDetailsService = AndroidApiDetailsService(requireContext())
|
||||||
|
|
||||||
|
dbService = AndroidDeviceDatabaseService(AndroidDeviceDatabase(requireContext()), SearchService(DateUtils(apiDetailsService)))
|
||||||
|
|
||||||
|
service = SelfossService(SelfossApi(apiDetailsService), dbService, SearchService(DateUtils(apiDetailsService)))
|
||||||
|
|
||||||
item = requireArguments().getParcelable(ARG_ITEMS)!!
|
item = requireArguments().getParcelable(ARG_ITEMS)!!
|
||||||
|
|
||||||
db = Room.databaseBuilder(
|
db = Room.databaseBuilder(
|
||||||
@ -110,8 +130,8 @@ class ArticleFragment : Fragment() {
|
|||||||
url = item.getLinkDecoded()
|
url = item.getLinkDecoded()
|
||||||
contentText = item.content
|
contentText = item.content
|
||||||
contentTitle = item.getTitleDecoded()
|
contentTitle = item.getTitleDecoded()
|
||||||
contentImage = item.getThumbnail(requireActivity())
|
contentImage = item.getThumbnail(apiDetailsService.getBaseUrl())
|
||||||
contentSource = item.sourceAndDateText()
|
contentSource = item.sourceAndDateText(DateUtils(apiDetailsService))
|
||||||
allImages = item.getImages()
|
allImages = item.getImages()
|
||||||
|
|
||||||
prefs = PreferenceManager.getDefaultSharedPreferences(activity)
|
prefs = PreferenceManager.getDefaultSharedPreferences(activity)
|
||||||
@ -136,10 +156,11 @@ class ArticleFragment : Fragment() {
|
|||||||
val settings = requireActivity().getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
|
val settings = requireActivity().getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
|
||||||
|
|
||||||
val api = SelfossApi(
|
val api = SelfossApi(
|
||||||
requireContext(),
|
// requireContext(),
|
||||||
requireActivity(),
|
// requireActivity(),
|
||||||
settings.getBoolean("isSelfSignedCert", false),
|
// settings.getBoolean("isSelfSignedCert", false),
|
||||||
prefs.getString("api_timeout", "-1")!!.toLong()
|
// prefs.getString("api_timeout", "-1")!!.toLong()
|
||||||
|
apiDetailsService
|
||||||
)
|
)
|
||||||
|
|
||||||
fab = binding.fab
|
fab = binding.fab
|
||||||
@ -166,27 +187,33 @@ class ArticleFragment : Fragment() {
|
|||||||
R.id.share_action -> requireActivity().shareLink(url, contentTitle)
|
R.id.share_action -> requireActivity().shareLink(url, contentTitle)
|
||||||
R.id.open_action -> requireActivity().openInBrowserAsNewTask(this@ArticleFragment.item)
|
R.id.open_action -> requireActivity().openInBrowserAsNewTask(this@ArticleFragment.item)
|
||||||
R.id.unread_action -> if (context != null) {
|
R.id.unread_action -> if (context != null) {
|
||||||
if (this@ArticleFragment.item.unread) {
|
if (this@ArticleFragment.item.unread == 1) {
|
||||||
SharedItems.readItem(
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
context!!,
|
// TODO:
|
||||||
api,
|
// dbService.readItem(
|
||||||
db,
|
// context!!,
|
||||||
this@ArticleFragment.item
|
// api,
|
||||||
)
|
// db,
|
||||||
this@ArticleFragment.item.unread = false
|
// this@ArticleFragment.item
|
||||||
|
// )
|
||||||
|
}
|
||||||
|
this@ArticleFragment.item.unread = 0
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
context,
|
context,
|
||||||
R.string.marked_as_read,
|
R.string.marked_as_read,
|
||||||
Toast.LENGTH_LONG
|
Toast.LENGTH_LONG
|
||||||
).show()
|
).show()
|
||||||
} else {
|
} else {
|
||||||
SharedItems.unreadItem(
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
context!!,
|
// TODO
|
||||||
api,
|
// .unreadItem(
|
||||||
db,
|
// context!!,
|
||||||
this@ArticleFragment.item
|
// api,
|
||||||
)
|
// db,
|
||||||
this@ArticleFragment.item.unread = true
|
// this@ArticleFragment.item
|
||||||
|
// )
|
||||||
|
}
|
||||||
|
this@ArticleFragment.item.unread = 1
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
context,
|
context,
|
||||||
R.string.marked_as_unread,
|
R.string.marked_as_unread,
|
||||||
@ -284,7 +311,7 @@ class ArticleFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getContentFromMercury(customTabsIntent: CustomTabsIntent) {
|
private fun getContentFromMercury(customTabsIntent: CustomTabsIntent) {
|
||||||
if ((context != null && requireContext().isNetworkAccessible(null)) || context == null) {
|
if ((context != null && requireContext().isNetworkAvailable(null)) || context == null) {
|
||||||
binding.progressBar.visibility = View.VISIBLE
|
binding.progressBar.visibility = View.VISIBLE
|
||||||
val parser = MercuryApi()
|
val parser = MercuryApi()
|
||||||
|
|
||||||
@ -535,11 +562,11 @@ class ArticleFragment : Fragment() {
|
|||||||
private const val ARG_ITEMS = "items"
|
private const val ARG_ITEMS = "items"
|
||||||
|
|
||||||
fun newInstance(
|
fun newInstance(
|
||||||
item: Item
|
item: SelfossModel.Item
|
||||||
): ArticleFragment {
|
): ArticleFragment {
|
||||||
val fragment = ArticleFragment()
|
val fragment = ArticleFragment()
|
||||||
val args = Bundle()
|
val args = Bundle()
|
||||||
args.putParcelable(ARG_ITEMS, item)
|
args.putParcelable(ARG_ITEMS, item.toParcelable())
|
||||||
fragment.arguments = args
|
fragment.arguments = args
|
||||||
return fragment
|
return fragment
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,125 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.android.model
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import android.text.Html
|
||||||
|
import android.webkit.URLUtil
|
||||||
|
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
|
import com.bumptech.glide.request.RequestOptions
|
||||||
|
import org.jsoup.Jsoup
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Items extension methods
|
||||||
|
*/
|
||||||
|
fun SelfossModel.Item.getIcon(baseUrl: String): String {
|
||||||
|
return constructUrl(baseUrl, "favicons", icon)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun SelfossModel.Item.getThumbnail(baseUrl: String): String {
|
||||||
|
return constructUrl(baseUrl, "thumbnails", thumbnail)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun SelfossModel.Item.getImages() : ArrayList<String> {
|
||||||
|
val allImages = ArrayList<String>()
|
||||||
|
|
||||||
|
for ( image in Jsoup.parse(content).getElementsByTag("img")) {
|
||||||
|
val url = image.attr("src")
|
||||||
|
if (url.lowercase(Locale.US).contains(".jpg") ||
|
||||||
|
url.lowercase(Locale.US).contains(".jpeg") ||
|
||||||
|
url.lowercase(Locale.US).contains(".png") ||
|
||||||
|
url.lowercase(Locale.US).contains(".webp"))
|
||||||
|
{
|
||||||
|
allImages.add(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return allImages
|
||||||
|
}
|
||||||
|
|
||||||
|
fun SelfossModel.Item.preloadImages(context: Context) : Boolean {
|
||||||
|
val imageUrls = this.getImages()
|
||||||
|
|
||||||
|
val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL).timeout(10000)
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (url in imageUrls) {
|
||||||
|
if ( URLUtil.isValidUrl(url)) {
|
||||||
|
Glide.with(context).asBitmap()
|
||||||
|
.apply(glideOptions)
|
||||||
|
.load(url).submit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e : Error) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun SelfossModel.Item.getTitleDecoded(): String {
|
||||||
|
return Html.fromHtml(title).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun SelfossModel.Item.getSourceTitle(): String {
|
||||||
|
return Html.fromHtml(sourcetitle).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: maybe find a better way to handle these kind of urls
|
||||||
|
fun SelfossModel.Item.getLinkDecoded(): String {
|
||||||
|
var stringUrl: String
|
||||||
|
stringUrl =
|
||||||
|
if (link.startsWith("http://news.google.com/news/") || link.startsWith("https://news.google.com/news/")) {
|
||||||
|
if (link.contains("&url=")) {
|
||||||
|
link.substringAfter("&url=")
|
||||||
|
} else {
|
||||||
|
this.link.replace("&", "&")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.link.replace("&", "&")
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle :443 => https
|
||||||
|
if (stringUrl.contains(":443")) {
|
||||||
|
stringUrl = stringUrl.replace(":443", "").replace("http://", "https://")
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle url not starting with http
|
||||||
|
if (stringUrl.startsWith("//")) {
|
||||||
|
stringUrl = "http:$stringUrl"
|
||||||
|
}
|
||||||
|
|
||||||
|
return stringUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sources extension methods
|
||||||
|
*/
|
||||||
|
|
||||||
|
fun SelfossModel.Source.getIcon(baseUrl: String): String {
|
||||||
|
return constructUrl(baseUrl, "favicons", icon)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun SelfossModel.Source.getTitleDecoded(): String {
|
||||||
|
return Html.fromHtml(title).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common methods
|
||||||
|
*/
|
||||||
|
private fun constructUrl(baseUrl: String, path: String, file: String?): String {
|
||||||
|
return if (file == null || file == "null" || file.isEmpty()) {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
val baseUriBuilder = Uri.parse(baseUrl).buildUpon()
|
||||||
|
baseUriBuilder.appendPath(path).appendPath(file)
|
||||||
|
|
||||||
|
baseUriBuilder.toString()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.android.model
|
||||||
|
|
||||||
|
import android.os.Parcel
|
||||||
|
import android.os.Parcelable
|
||||||
|
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
fun SelfossModel.Item.toParcelable() : ParecelableItem =
|
||||||
|
ParecelableItem(
|
||||||
|
this.id,
|
||||||
|
this.datetime,
|
||||||
|
this.title,
|
||||||
|
this.content,
|
||||||
|
this.unread,
|
||||||
|
this.starred,
|
||||||
|
this.thumbnail,
|
||||||
|
this.icon,
|
||||||
|
this.link,
|
||||||
|
this.sourcetitle,
|
||||||
|
this.tags
|
||||||
|
)
|
||||||
|
data class ParecelableItem(
|
||||||
|
@SerializedName("id") val id: String,
|
||||||
|
@SerializedName("datetime") val datetime: String,
|
||||||
|
@SerializedName("title") val title: String,
|
||||||
|
@SerializedName("content") val content: String,
|
||||||
|
@SerializedName("unread") var unread: Int,
|
||||||
|
@SerializedName("starred") var starred: Int,
|
||||||
|
@SerializedName("thumbnail") val thumbnail: String?,
|
||||||
|
@SerializedName("icon") val icon: String?,
|
||||||
|
@SerializedName("link") val link: String,
|
||||||
|
@SerializedName("sourcetitle") val sourcetitle: String,
|
||||||
|
@SerializedName("tags") val tags: String
|
||||||
|
) : Parcelable {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmField
|
||||||
|
val CREATOR: Parcelable.Creator<ParecelableItem> = object : Parcelable.Creator<ParecelableItem> {
|
||||||
|
override fun createFromParcel(source: Parcel): ParecelableItem = ParecelableItem(source)
|
||||||
|
override fun newArray(size: Int): Array<ParecelableItem?> = arrayOfNulls(size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(source: Parcel) : this(
|
||||||
|
id = source.readString().orEmpty(),
|
||||||
|
datetime = source.readString().orEmpty(),
|
||||||
|
title = source.readString().orEmpty(),
|
||||||
|
content = source.readString().orEmpty(),
|
||||||
|
unread = source.readInt(),
|
||||||
|
starred = source.readInt(),
|
||||||
|
thumbnail = source.readString(),
|
||||||
|
icon = source.readString(),
|
||||||
|
link = source.readString().orEmpty(),
|
||||||
|
sourcetitle = source.readString().orEmpty(),
|
||||||
|
tags = source.readString().orEmpty()
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun describeContents() = 0
|
||||||
|
|
||||||
|
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||||
|
dest.writeString(id)
|
||||||
|
dest.writeString(datetime)
|
||||||
|
dest.writeString(title)
|
||||||
|
dest.writeString(content)
|
||||||
|
dest.writeInt(unread)
|
||||||
|
dest.writeInt(starred)
|
||||||
|
dest.writeString(thumbnail)
|
||||||
|
dest.writeString(icon)
|
||||||
|
dest.writeString(link)
|
||||||
|
dest.writeString(sourcetitle)
|
||||||
|
dest.writeString(tags)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.android.persistence
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.room.Room
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.persistence.entities.AndroidItemEntity
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_1_2
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_2_3
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_3_4
|
||||||
|
import bou.amine.apps.readerforselfossv2.dao.DeviceDatabase
|
||||||
|
|
||||||
|
class AndroidDeviceDatabase(applicationContext: Context): DeviceDatabase<AndroidItemEntity> {
|
||||||
|
var db: AppDatabase = Room.databaseBuilder(
|
||||||
|
applicationContext,
|
||||||
|
AppDatabase::class.java, "selfoss-database"
|
||||||
|
).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).addMigrations(MIGRATION_3_4).build()
|
||||||
|
|
||||||
|
|
||||||
|
override suspend fun items(): List<AndroidItemEntity> = db.itemsDao().items()
|
||||||
|
|
||||||
|
override suspend fun insertAllItems(vararg items: AndroidItemEntity) = db.itemsDao().insertAllItems(*items)
|
||||||
|
|
||||||
|
override suspend fun deleteAllItems() = db.itemsDao().deleteAllItems()
|
||||||
|
|
||||||
|
override suspend fun delete(item: AndroidItemEntity) = db.itemsDao().delete(item)
|
||||||
|
|
||||||
|
override suspend fun updateItem(item: AndroidItemEntity) = db.itemsDao().updateItem(item)
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.android.persistence
|
||||||
|
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.persistence.entities.AndroidItemEntity
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.utils.persistence.toEntity
|
||||||
|
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||||
|
import bou.amine.apps.readerforselfossv2.service.DeviceDataBaseService
|
||||||
|
import bou.amine.apps.readerforselfossv2.service.SearchService
|
||||||
|
|
||||||
|
class AndroidDeviceDatabaseService(db: AndroidDeviceDatabase, searchService: SearchService) :
|
||||||
|
DeviceDataBaseService<AndroidItemEntity>(db, searchService) {
|
||||||
|
override suspend fun updateDatabase() {
|
||||||
|
if (itemsCaching) {
|
||||||
|
if (items.isEmpty()) {
|
||||||
|
getFromDB()
|
||||||
|
}
|
||||||
|
db.deleteAllItems()
|
||||||
|
db.insertAllItems(*(items.map { it.toEntity() }).toTypedArray())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun clearDBItems() {
|
||||||
|
db.deleteAllItems()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun appendNewItems(newItems: List<SelfossModel.Item>) {
|
||||||
|
var tmpItems = items
|
||||||
|
if (tmpItems != newItems) {
|
||||||
|
tmpItems = tmpItems.filter { item -> newItems.find { it.id == item.id } == null } as ArrayList<SelfossModel.Item>
|
||||||
|
tmpItems.addAll(newItems)
|
||||||
|
items = tmpItems
|
||||||
|
|
||||||
|
sortItems()
|
||||||
|
getFocusedItems()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFromDB() {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,7 @@ import androidx.room.Delete
|
|||||||
import androidx.room.Insert
|
import androidx.room.Insert
|
||||||
import androidx.room.OnConflictStrategy
|
import androidx.room.OnConflictStrategy
|
||||||
import androidx.room.Query
|
import androidx.room.Query
|
||||||
import bou.amine.apps.readerforselfossv2.android.persistence.entities.ItemEntity
|
import bou.amine.apps.readerforselfossv2.android.persistence.entities.AndroidItemEntity
|
||||||
import androidx.room.Update
|
import androidx.room.Update
|
||||||
|
|
||||||
|
|
||||||
@ -13,17 +13,17 @@ import androidx.room.Update
|
|||||||
@Dao
|
@Dao
|
||||||
interface ItemsDao {
|
interface ItemsDao {
|
||||||
@Query("SELECT * FROM items order by id desc")
|
@Query("SELECT * FROM items order by id desc")
|
||||||
suspend fun items(): List<ItemEntity>
|
suspend fun items(): List<AndroidItemEntity>
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
suspend fun insertAllItems(vararg items: ItemEntity)
|
suspend fun insertAllItems(vararg items: AndroidItemEntity)
|
||||||
|
|
||||||
@Query("DELETE FROM items")
|
@Query("DELETE FROM items")
|
||||||
suspend fun deleteAllItems()
|
suspend fun deleteAllItems()
|
||||||
|
|
||||||
@Delete
|
@Delete
|
||||||
suspend fun delete(item: ItemEntity)
|
suspend fun delete(item: AndroidItemEntity)
|
||||||
|
|
||||||
@Update
|
@Update
|
||||||
suspend fun updateItem(item: ItemEntity)
|
suspend fun updateItem(item: AndroidItemEntity)
|
||||||
}
|
}
|
@ -6,11 +6,11 @@ import bou.amine.apps.readerforselfossv2.android.persistence.dao.ActionsDao
|
|||||||
import bou.amine.apps.readerforselfossv2.android.persistence.dao.DrawerDataDao
|
import bou.amine.apps.readerforselfossv2.android.persistence.dao.DrawerDataDao
|
||||||
import bou.amine.apps.readerforselfossv2.android.persistence.dao.ItemsDao
|
import bou.amine.apps.readerforselfossv2.android.persistence.dao.ItemsDao
|
||||||
import bou.amine.apps.readerforselfossv2.android.persistence.entities.ActionEntity
|
import bou.amine.apps.readerforselfossv2.android.persistence.entities.ActionEntity
|
||||||
import bou.amine.apps.readerforselfossv2.android.persistence.entities.ItemEntity
|
import bou.amine.apps.readerforselfossv2.android.persistence.entities.AndroidItemEntity
|
||||||
import bou.amine.apps.readerforselfossv2.android.persistence.entities.SourceEntity
|
import bou.amine.apps.readerforselfossv2.android.persistence.entities.SourceEntity
|
||||||
import bou.amine.apps.readerforselfossv2.android.persistence.entities.TagEntity
|
import bou.amine.apps.readerforselfossv2.android.persistence.entities.TagEntity
|
||||||
|
|
||||||
@Database(entities = [TagEntity::class, SourceEntity::class, ItemEntity::class, ActionEntity::class], version = 4)
|
@Database(entities = [TagEntity::class, SourceEntity::class, AndroidItemEntity::class, ActionEntity::class], version = 4)
|
||||||
abstract class AppDatabase : RoomDatabase() {
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
abstract fun drawerDataDao(): DrawerDataDao
|
abstract fun drawerDataDao(): DrawerDataDao
|
||||||
|
|
||||||
|
@ -3,9 +3,10 @@ package bou.amine.apps.readerforselfossv2.android.persistence.entities
|
|||||||
import androidx.room.ColumnInfo
|
import androidx.room.ColumnInfo
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
|
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||||
|
|
||||||
@Entity(tableName = "items")
|
@Entity(tableName = "items")
|
||||||
data class ItemEntity(
|
data class AndroidItemEntity(
|
||||||
@PrimaryKey
|
@PrimaryKey
|
||||||
@ColumnInfo(name = "id")
|
@ColumnInfo(name = "id")
|
||||||
val id: String,
|
val id: String,
|
@ -0,0 +1,47 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.android.service
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
|
||||||
|
|
||||||
|
class AndroidApiDetailsService(c: Context) : ApiDetailsService {
|
||||||
|
val settings: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(c)
|
||||||
|
private var apiVersion: Int = -1
|
||||||
|
private var baseUrl: String = ""
|
||||||
|
private var userName: String = ""
|
||||||
|
private var password: String = ""
|
||||||
|
override fun logApiCalls(message: String) {
|
||||||
|
Log.d("LogApiCalls", message)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun getApiVersion(): Int {
|
||||||
|
if (apiVersion != -1) {
|
||||||
|
apiVersion = settings.getInt("apiVersion", -1)!!
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +0,0 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.android.utils
|
|
||||||
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.api.selfoss.SuccessResponse
|
|
||||||
import retrofit2.Response
|
|
||||||
|
|
||||||
fun Response<SuccessResponse>.succeeded(): Boolean =
|
|
||||||
this.code() === 200 && this.body() != null && this.body()!!.isSuccess
|
|
@ -3,27 +3,7 @@ package bou.amine.apps.readerforselfossv2.android.utils
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import bou.amine.apps.readerforselfossv2.android.R
|
import bou.amine.apps.readerforselfossv2.android.R
|
||||||
|
import bou.amine.apps.readerforselfossv2.utils.toStringUriWithHttp
|
||||||
fun String?.isEmptyOrNullOrNullString(): Boolean =
|
|
||||||
this == null || this == "null" || this.isEmpty()
|
|
||||||
|
|
||||||
fun String.longHash(): Long {
|
|
||||||
var h = 98764321261L
|
|
||||||
val l = this.length
|
|
||||||
val chars = this.toCharArray()
|
|
||||||
|
|
||||||
for (i in 0 until l) {
|
|
||||||
h = 31 * h + chars[i].code.toLong()
|
|
||||||
}
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
fun String.toStringUriWithHttp(): String =
|
|
||||||
if (!this.startsWith("https://") && !this.startsWith("http://")) {
|
|
||||||
"http://" + this
|
|
||||||
} else {
|
|
||||||
this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Context.shareLink(itemUrl: String, itemTitle: String) {
|
fun Context.shareLink(itemUrl: String, itemTitle: String) {
|
||||||
val sendIntent = Intent()
|
val sendIntent = Intent()
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.android.utils
|
|
||||||
|
|
||||||
import android.text.format.DateUtils
|
|
||||||
import java.time.Instant
|
|
||||||
import java.time.LocalDateTime
|
|
||||||
import java.time.OffsetDateTime
|
|
||||||
import java.time.ZoneOffset
|
|
||||||
import java.time.format.DateTimeFormatter
|
|
||||||
|
|
||||||
fun parseDate(dateString: String): Instant {
|
|
||||||
|
|
||||||
val FORMATTERV1 = "yyyy-MM-dd HH:mm:ss"
|
|
||||||
|
|
||||||
return if (Config.apiVersion >= 4) {
|
|
||||||
OffsetDateTime.parse(dateString).toInstant()
|
|
||||||
} else {
|
|
||||||
LocalDateTime.parse(dateString, DateTimeFormatter.ofPattern(FORMATTERV1)).toInstant(ZoneOffset.UTC)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun parseRelativeDate(dateString: String): String {
|
|
||||||
|
|
||||||
val date = parseDate(dateString)
|
|
||||||
|
|
||||||
return " " + DateUtils.getRelativeTimeSpanString(
|
|
||||||
date.toEpochMilli(),
|
|
||||||
Instant.now().toEpochMilli(),
|
|
||||||
DateUtils.MINUTE_IN_MILLIS,
|
|
||||||
DateUtils.FORMAT_ABBREV_RELATIVE
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,8 +1,10 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.android.utils
|
package bou.amine.apps.readerforselfossv2.android.utils
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import bou.amine.apps.readerforselfossv2.android.api.selfoss.Item
|
import bou.amine.apps.readerforselfossv2.android.model.getSourceTitle
|
||||||
import bou.amine.apps.readerforselfossv2.android.api.selfoss.SelfossTagType
|
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||||
|
import bou.amine.apps.readerforselfossv2.utils.DateUtils
|
||||||
|
import bou.amine.apps.readerforselfossv2.utils.parseRelativeDate
|
||||||
|
|
||||||
fun String.toTextDrawableString(c: Context): String {
|
fun String.toTextDrawableString(c: Context): String {
|
||||||
val textDrawable = StringBuilder()
|
val textDrawable = StringBuilder()
|
||||||
@ -15,22 +17,22 @@ fun String.toTextDrawableString(c: Context): String {
|
|||||||
return textDrawable.toString()
|
return textDrawable.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Item.sourceAndDateText(): String {
|
fun SelfossModel.Item.sourceAndDateText(dateUtils: DateUtils): String {
|
||||||
val formattedDate = parseRelativeDate(this.datetime)
|
val formattedDate = parseRelativeDate(dateUtils)
|
||||||
|
|
||||||
return this.getSourceTitle() + formattedDate
|
return getSourceTitle() + formattedDate
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Item.toggleStar(): Item {
|
fun SelfossModel.Item.toggleStar(): SelfossModel.Item {
|
||||||
this.starred = !this.starred
|
this.starred = if (this.starred == 0) 1 else 0
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
fun List<Item>.flattenTags(): List<Item> =
|
fun List<SelfossModel.Item>.flattenTags(): List<SelfossModel.Item> =
|
||||||
this.flatMap {
|
this.flatMap {
|
||||||
val item = it
|
val item = it
|
||||||
val tags: List<String> = it.tags.tags.split(",")
|
val tags: List<String> = it.tags.split(",")
|
||||||
tags.map { t ->
|
tags.map { t ->
|
||||||
item.copy(tags = SelfossTagType(t.trim()))
|
item.copy(tags = t.trim())
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -18,8 +18,11 @@ import android.widget.TextView
|
|||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import bou.amine.apps.readerforselfossv2.android.R
|
import bou.amine.apps.readerforselfossv2.android.R
|
||||||
import bou.amine.apps.readerforselfossv2.android.ReaderActivity
|
import bou.amine.apps.readerforselfossv2.android.ReaderActivity
|
||||||
import bou.amine.apps.readerforselfossv2.android.api.selfoss.Item
|
import bou.amine.apps.readerforselfossv2.android.model.getLinkDecoded
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper
|
import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper
|
||||||
|
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||||
|
import bou.amine.apps.readerforselfossv2.service.SearchService
|
||||||
|
import bou.amine.apps.readerforselfossv2.utils.toStringUriWithHttp
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
|
|
||||||
fun Context.buildCustomTabsIntent(): CustomTabsIntent {
|
fun Context.buildCustomTabsIntent(): CustomTabsIntent {
|
||||||
@ -71,16 +74,17 @@ fun Context.buildCustomTabsIntent(): CustomTabsIntent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun Context.openItemUrlInternally(
|
fun Context.openItemUrlInternally(
|
||||||
allItems: ArrayList<Item>,
|
allItems: ArrayList<SelfossModel.Item>,
|
||||||
currentItem: Int,
|
currentItem: Int,
|
||||||
linkDecoded: String,
|
linkDecoded: String,
|
||||||
customTabsIntent: CustomTabsIntent,
|
customTabsIntent: CustomTabsIntent,
|
||||||
articleViewer: Boolean,
|
articleViewer: Boolean,
|
||||||
app: Activity
|
app: Activity,
|
||||||
|
searchService: SearchService
|
||||||
) {
|
) {
|
||||||
if (articleViewer) {
|
if (articleViewer) {
|
||||||
ReaderActivity.allItems = allItems
|
ReaderActivity.allItems = allItems
|
||||||
SharedItems.position = currentItem
|
searchService.position = currentItem
|
||||||
val intent = Intent(this, ReaderActivity::class.java)
|
val intent = Intent(this, ReaderActivity::class.java)
|
||||||
intent.putExtra("currentItem", currentItem)
|
intent.putExtra("currentItem", currentItem)
|
||||||
app.startActivity(intent)
|
app.startActivity(intent)
|
||||||
@ -113,13 +117,14 @@ fun Context.openItemUrlInternalBrowser(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun Context.openItemUrl(
|
fun Context.openItemUrl(
|
||||||
allItems: ArrayList<Item>,
|
allItems: ArrayList<SelfossModel.Item>,
|
||||||
currentItem: Int,
|
currentItem: Int,
|
||||||
linkDecoded: String,
|
linkDecoded: String,
|
||||||
customTabsIntent: CustomTabsIntent,
|
customTabsIntent: CustomTabsIntent,
|
||||||
internalBrowser: Boolean,
|
internalBrowser: Boolean,
|
||||||
articleViewer: Boolean,
|
articleViewer: Boolean,
|
||||||
app: Activity
|
app: Activity,
|
||||||
|
searchService: SearchService
|
||||||
) {
|
) {
|
||||||
|
|
||||||
if (!linkDecoded.isUrlValid()) {
|
if (!linkDecoded.isUrlValid()) {
|
||||||
@ -138,7 +143,8 @@ fun Context.openItemUrl(
|
|||||||
linkDecoded,
|
linkDecoded,
|
||||||
customTabsIntent,
|
customTabsIntent,
|
||||||
articleViewer,
|
articleViewer,
|
||||||
app
|
app,
|
||||||
|
searchService
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
this.openItemUrlInternalBrowser(
|
this.openItemUrlInternalBrowser(
|
||||||
@ -174,7 +180,7 @@ fun String.isBaseUrlValid(ctx: Context): Boolean {
|
|||||||
return Patterns.WEB_URL.matcher(this).matches() && existsAndEndsWithSlash
|
return Patterns.WEB_URL.matcher(this).matches() && existsAndEndsWithSlash
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Context.openInBrowserAsNewTask(i: Item) {
|
fun Context.openInBrowserAsNewTask(i: SelfossModel.Item) {
|
||||||
val intent = Intent(Intent.ACTION_VIEW)
|
val intent = Intent(Intent.ACTION_VIEW)
|
||||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
intent.data = Uri.parse(i.getLinkDecoded().toStringUriWithHttp())
|
intent.data = Uri.parse(i.getLinkDecoded().toStringUriWithHttp())
|
||||||
|
@ -1,403 +0,0 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.android.utils
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.widget.Toast
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.R
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.api.selfoss.Item
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.api.selfoss.SelfossApi
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.api.selfoss.SuccessResponse
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.persistence.entities.ActionEntity
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.persistence.toEntity
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAccessible
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.persistence.toView
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import retrofit2.Call
|
|
||||||
import retrofit2.Callback
|
|
||||||
import retrofit2.Response
|
|
||||||
import kotlin.concurrent.thread
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Singleton class that contains the articles fetched from Selfoss, it allows sharing the items list
|
|
||||||
* between Activities and Fragments
|
|
||||||
*/
|
|
||||||
object SharedItems {
|
|
||||||
var items: ArrayList<Item> = arrayListOf<Item>()
|
|
||||||
get() {
|
|
||||||
return ArrayList(field)
|
|
||||||
}
|
|
||||||
set(value) {
|
|
||||||
field = ArrayList(value)
|
|
||||||
}
|
|
||||||
var focusedItems: ArrayList<Item> = arrayListOf<Item>()
|
|
||||||
get() {
|
|
||||||
return ArrayList(field)
|
|
||||||
}
|
|
||||||
set(value) {
|
|
||||||
field = ArrayList(value)
|
|
||||||
}
|
|
||||||
var position = 0
|
|
||||||
set(value) {
|
|
||||||
field = when {
|
|
||||||
value < 0 -> 0
|
|
||||||
value > items.size -> items.size
|
|
||||||
else -> value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var displayedItems: String = "unread"
|
|
||||||
set(value) {
|
|
||||||
field = when (value) {
|
|
||||||
"all" -> "all"
|
|
||||||
"unread" -> "unread"
|
|
||||||
"read" -> "read"
|
|
||||||
"starred" -> "starred"
|
|
||||||
else -> "all"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var searchFilter: String? = null
|
|
||||||
var sourceIDFilter: Long? = null
|
|
||||||
var sourceFilter: String? = null
|
|
||||||
var tagFilter: String? = null
|
|
||||||
var itemsCaching = false
|
|
||||||
|
|
||||||
var fetchedUnread = false
|
|
||||||
var fetchedAll = false
|
|
||||||
var fetchedStarred = false
|
|
||||||
|
|
||||||
var badgeUnread = -1
|
|
||||||
var badgeAll = -1
|
|
||||||
var badgeStarred = -1
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add new items to the SharedItems list
|
|
||||||
*
|
|
||||||
* The new items are considered more updated than the ones already in the list.
|
|
||||||
* The old items present in the new list are discarded and replaced by the new ones.
|
|
||||||
* Items are compared according to the selfoss id, which should always be unique.
|
|
||||||
*/
|
|
||||||
fun appendNewItems(newItems: ArrayList<Item>) {
|
|
||||||
var tmpItems = items
|
|
||||||
if (tmpItems != newItems) {
|
|
||||||
tmpItems = tmpItems.filter { item -> newItems.find { it.id == item.id } == null } as ArrayList<Item>
|
|
||||||
tmpItems.addAll(newItems)
|
|
||||||
items = tmpItems
|
|
||||||
|
|
||||||
sortItems()
|
|
||||||
getFocusedItems()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun refreshFocusedItems(newItems: ArrayList<Item>) {
|
|
||||||
val tmpItems = items
|
|
||||||
tmpItems.removeAll(focusedItems)
|
|
||||||
|
|
||||||
appendNewItems(newItems)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun clearDBItems(db: AppDatabase) {
|
|
||||||
db.itemsDao().deleteAllItems()
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun updateDatabase(db: AppDatabase) {
|
|
||||||
if (itemsCaching) {
|
|
||||||
if (items.isEmpty()) {
|
|
||||||
getFromDB(db)
|
|
||||||
}
|
|
||||||
db.itemsDao().deleteAllItems()
|
|
||||||
db.itemsDao().insertAllItems(*(items.map { it.toEntity() }).toTypedArray())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun filter() {
|
|
||||||
fun filterSearch(item: Item): Boolean {
|
|
||||||
return if (!searchFilter.isEmptyOrNullOrNullString()) {
|
|
||||||
var matched = item.title.contains(searchFilter.toString(), true)
|
|
||||||
matched = matched || item.content.contains(searchFilter.toString(), true)
|
|
||||||
matched = matched || item.sourcetitle.contains(searchFilter.toString(), true)
|
|
||||||
matched
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var tmpItems = focusedItems
|
|
||||||
if (tagFilter != null) {
|
|
||||||
tmpItems = tmpItems.filter { it.tags.tags.contains(tagFilter.toString()) } as ArrayList<Item>
|
|
||||||
}
|
|
||||||
if (searchFilter != null) {
|
|
||||||
tmpItems = tmpItems.filter { filterSearch(it) } as ArrayList<Item>
|
|
||||||
}
|
|
||||||
if (sourceFilter != null) {
|
|
||||||
tmpItems = tmpItems.filter { it.sourcetitle == sourceFilter } as ArrayList<Item>
|
|
||||||
}
|
|
||||||
focusedItems = tmpItems
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getFocusedItems() {
|
|
||||||
when (displayedItems) {
|
|
||||||
"all" -> getAll()
|
|
||||||
"unread" -> getUnRead()
|
|
||||||
"read" -> getRead()
|
|
||||||
"starred" -> getStarred()
|
|
||||||
else -> getUnRead()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getUnRead() {
|
|
||||||
displayedItems = "unread"
|
|
||||||
focusedItems = items.filter { item -> item.unread } as ArrayList<Item>
|
|
||||||
filter()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getRead() {
|
|
||||||
displayedItems = "read"
|
|
||||||
focusedItems = items.filter { item -> !item.unread } as ArrayList<Item>
|
|
||||||
filter()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getStarred() {
|
|
||||||
displayedItems = "starred"
|
|
||||||
focusedItems = items.filter { item -> item.starred } as ArrayList<Item>
|
|
||||||
filter()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getAll() {
|
|
||||||
displayedItems = "all"
|
|
||||||
focusedItems = items
|
|
||||||
filter()
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getFromDB(db: AppDatabase) {
|
|
||||||
if (itemsCaching) {
|
|
||||||
val dbItems = db.itemsDao().items().map { it.toView() } as ArrayList<Item>
|
|
||||||
appendNewItems(dbItems)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun removeItemAtIndex(index: Int) {
|
|
||||||
val i = focusedItems[index]
|
|
||||||
val tmpItems = focusedItems
|
|
||||||
tmpItems.remove(i)
|
|
||||||
focusedItems = tmpItems
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addItemAtIndex(newItem: Item, index: Int) {
|
|
||||||
val tmpItems = focusedItems
|
|
||||||
tmpItems.add(index, newItem)
|
|
||||||
focusedItems = tmpItems
|
|
||||||
}
|
|
||||||
|
|
||||||
fun readItem(app: Context, api: SelfossApi, db: AppDatabase, item: Item) {
|
|
||||||
if (items.contains(item)) {
|
|
||||||
position = items.indexOf(item)
|
|
||||||
readItemAtPosition(app, api, db)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun readItems(db: AppDatabase, ids: List<String>) {
|
|
||||||
for (id in ids) {
|
|
||||||
val match = items.filter { it -> it.id == id }
|
|
||||||
if (match.isNotEmpty() && match.size == 1) {
|
|
||||||
position = items.indexOf(match[0])
|
|
||||||
val tmpItems = items
|
|
||||||
tmpItems[position].unread = false
|
|
||||||
items = tmpItems
|
|
||||||
resetDBItem(db)
|
|
||||||
badgeUnread--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun readItemAtPosition(app: Context, api: SelfossApi, db: AppDatabase) {
|
|
||||||
val i = items[position]
|
|
||||||
|
|
||||||
if (app.isNetworkAccessible(null)) {
|
|
||||||
api.markItem(i.id).enqueue(object : Callback<SuccessResponse> {
|
|
||||||
override fun onResponse(
|
|
||||||
call: Call<SuccessResponse>,
|
|
||||||
response: Response<SuccessResponse>
|
|
||||||
) {
|
|
||||||
|
|
||||||
val tmpItems = items
|
|
||||||
tmpItems[position].unread = false
|
|
||||||
items = tmpItems
|
|
||||||
|
|
||||||
resetDBItem(db)
|
|
||||||
getFocusedItems()
|
|
||||||
badgeUnread--
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
|
|
||||||
Toast.makeText(
|
|
||||||
app,
|
|
||||||
app.getString(R.string.cant_mark_read),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else if (itemsCaching) {
|
|
||||||
thread {
|
|
||||||
db.actionsDao().insertAllActions(ActionEntity(i.id, true, false, false, false))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (position > items.size) {
|
|
||||||
position -= 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun unreadItem(app: Context, api: SelfossApi, db: AppDatabase, item: Item) {
|
|
||||||
if (items.contains(item) && !item.unread) {
|
|
||||||
position = items.indexOf(item)
|
|
||||||
unreadItemAtPosition(app, api, db)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun unreadItemAtPosition(app: Context, api: SelfossApi, db: AppDatabase) {
|
|
||||||
val i = items[position]
|
|
||||||
|
|
||||||
if (app.isNetworkAccessible(null)) {
|
|
||||||
api.unmarkItem(i.id).enqueue(object : Callback<SuccessResponse> {
|
|
||||||
override fun onResponse(
|
|
||||||
call: Call<SuccessResponse>,
|
|
||||||
response: Response<SuccessResponse>
|
|
||||||
) {
|
|
||||||
|
|
||||||
val tmpItems = items
|
|
||||||
tmpItems[position].unread = true
|
|
||||||
items = tmpItems
|
|
||||||
|
|
||||||
resetDBItem(db)
|
|
||||||
getFocusedItems()
|
|
||||||
badgeUnread++
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
|
|
||||||
Toast.makeText(
|
|
||||||
app,
|
|
||||||
app.getString(R.string.cant_mark_unread),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else if (itemsCaching) {
|
|
||||||
thread {
|
|
||||||
db.actionsDao().insertAllActions(ActionEntity(i.id, false, true, false, false))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun starItem(app: Context, api: SelfossApi, db: AppDatabase, item: Item) {
|
|
||||||
if (items.contains(item) && !item.starred) {
|
|
||||||
position = items.indexOf(item)
|
|
||||||
starItemAtPosition(app, api, db)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun starItemAtPosition(app: Context, api: SelfossApi, db: AppDatabase) {
|
|
||||||
val i = items[position]
|
|
||||||
|
|
||||||
if (app.isNetworkAccessible(null)) {
|
|
||||||
api.starrItem(i.id).enqueue(object : Callback<SuccessResponse> {
|
|
||||||
override fun onResponse(
|
|
||||||
call: Call<SuccessResponse>,
|
|
||||||
response: Response<SuccessResponse>
|
|
||||||
) {
|
|
||||||
val tmpItems = items
|
|
||||||
tmpItems[position].starred = true
|
|
||||||
items = tmpItems
|
|
||||||
|
|
||||||
resetDBItem(db)
|
|
||||||
getFocusedItems()
|
|
||||||
badgeStarred++
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(
|
|
||||||
call: Call<SuccessResponse>,
|
|
||||||
t: Throwable
|
|
||||||
) {
|
|
||||||
Toast.makeText(
|
|
||||||
app,
|
|
||||||
app.getString(R.string.cant_mark_favortie),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
thread {
|
|
||||||
db.actionsDao().insertAllActions(ActionEntity(i.id, false, false, true, false))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun unstarItem(app: Context, api: SelfossApi, db: AppDatabase, item: Item) {
|
|
||||||
if (items.contains(item) && item.starred) {
|
|
||||||
position = items.indexOf(item)
|
|
||||||
unstarItemAtPosition(app, api, db)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun unstarItemAtPosition(app: Context, api: SelfossApi, db: AppDatabase) {
|
|
||||||
val i = items[position]
|
|
||||||
|
|
||||||
if (app.isNetworkAccessible(null)) {
|
|
||||||
api.unstarrItem(i.id).enqueue(object : Callback<SuccessResponse> {
|
|
||||||
override fun onResponse(
|
|
||||||
call: Call<SuccessResponse>,
|
|
||||||
response: Response<SuccessResponse>
|
|
||||||
) {
|
|
||||||
val tmpItems = items
|
|
||||||
tmpItems[position].starred = false
|
|
||||||
items = tmpItems
|
|
||||||
|
|
||||||
resetDBItem(db)
|
|
||||||
getFocusedItems()
|
|
||||||
badgeStarred--
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(
|
|
||||||
call: Call<SuccessResponse>,
|
|
||||||
t: Throwable
|
|
||||||
) {
|
|
||||||
Toast.makeText(
|
|
||||||
app,
|
|
||||||
app.getString(R.string.cant_unmark_favortie),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
thread {
|
|
||||||
db.actionsDao().insertAllActions(ActionEntity(i.id, false, false, false, true))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun resetDBItem(db: AppDatabase) {
|
|
||||||
if (itemsCaching) {
|
|
||||||
val i = items[position]
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
db.itemsDao().delete(i.toEntity())
|
|
||||||
db.itemsDao().insertAllItems(i.toEntity())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun unreadItemStatusAtIndex(position: Int): Boolean {
|
|
||||||
return focusedItems[position].unread
|
|
||||||
}
|
|
||||||
|
|
||||||
fun computeBadges() {
|
|
||||||
badgeUnread = items.filter { item -> item.unread }.size
|
|
||||||
badgeStarred = items.filter { item -> item.starred }.size
|
|
||||||
badgeAll = items.size
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun sortItems() {
|
|
||||||
val tmpItems = ArrayList(items.sortedByDescending { parseDate(it.datetime) })
|
|
||||||
items = tmpItems
|
|
||||||
}
|
|
||||||
}
|
|
@ -14,8 +14,11 @@ var snackBarShown = false
|
|||||||
var view: View? = null
|
var view: View? = null
|
||||||
lateinit var s: Snackbar
|
lateinit var s: Snackbar
|
||||||
|
|
||||||
fun Context.isNetworkAccessible(v: View?, overrideOffline: Boolean = false): Boolean {
|
fun Context.isNetworkAvailable(
|
||||||
val networkIsAccessible = isNetworkAvailable(this)
|
v: View? = null,
|
||||||
|
overrideOffline: Boolean = false
|
||||||
|
): Boolean {
|
||||||
|
val networkIsAccessible = isNetworkAccessible(this)
|
||||||
|
|
||||||
if (v != null && (!networkIsAccessible || overrideOffline) && (!snackBarShown || v != view)) {
|
if (v != null && (!networkIsAccessible || overrideOffline) && (!snackBarShown || v != view)) {
|
||||||
view = v
|
view = v
|
||||||
@ -43,22 +46,22 @@ fun Context.isNetworkAccessible(v: View?, overrideOffline: Boolean = false): Boo
|
|||||||
return if(overrideOffline) overrideOffline else networkIsAccessible
|
return if(overrideOffline) overrideOffline else networkIsAccessible
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isNetworkAvailable(context: Context): Boolean {
|
private fun isNetworkAccessible(context: Context): Boolean {
|
||||||
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
val network = connectivityManager.activeNetwork ?: return false
|
val network = connectivityManager.activeNetwork ?: return false
|
||||||
val networkCapabilities = connectivityManager.getNetworkCapabilities(network) ?: return false
|
val networkCapabilities = connectivityManager.getNetworkCapabilities(network) ?: return false
|
||||||
|
|
||||||
return when {
|
return when {
|
||||||
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH) -> true
|
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH) -> true
|
||||||
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
|
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
|
||||||
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
|
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
|
||||||
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
|
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val network = connectivityManager.activeNetworkInfo ?: return false
|
val network = connectivityManager.activeNetworkInfo ?: return false
|
||||||
return network.isConnectedOrConnecting
|
return network.isConnectedOrConnecting
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,73 +1,72 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.android.utils.persistence
|
package bou.amine.apps.readerforselfossv2.android.utils.persistence
|
||||||
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.api.selfoss.Item
|
import bou.amine.apps.readerforselfossv2.android.model.getSourceTitle
|
||||||
import bou.amine.apps.readerforselfossv2.android.api.selfoss.SelfossTagType
|
import bou.amine.apps.readerforselfossv2.android.model.getTitleDecoded
|
||||||
import bou.amine.apps.readerforselfossv2.android.api.selfoss.Source
|
import bou.amine.apps.readerforselfossv2.android.persistence.entities.AndroidItemEntity
|
||||||
import bou.amine.apps.readerforselfossv2.android.api.selfoss.Tag
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.persistence.entities.ItemEntity
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.persistence.entities.SourceEntity
|
import bou.amine.apps.readerforselfossv2.android.persistence.entities.SourceEntity
|
||||||
import bou.amine.apps.readerforselfossv2.android.persistence.entities.TagEntity
|
import bou.amine.apps.readerforselfossv2.android.persistence.entities.TagEntity
|
||||||
|
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||||
|
|
||||||
fun TagEntity.toView(): Tag =
|
fun TagEntity.toView(): SelfossModel.Tag =
|
||||||
Tag(
|
SelfossModel.Tag(
|
||||||
this.tag,
|
this.tag,
|
||||||
this.color,
|
this.color,
|
||||||
this.unread
|
this.unread
|
||||||
)
|
)
|
||||||
|
|
||||||
fun SourceEntity.toView(): Source =
|
fun SourceEntity.toView(): SelfossModel.Source =
|
||||||
Source(
|
SelfossModel.Source(
|
||||||
this.id,
|
this.id,
|
||||||
this.title,
|
this.title,
|
||||||
SelfossTagType(this.tags),
|
this.tags.split(","),
|
||||||
this.spout,
|
this.spout,
|
||||||
this.error,
|
this.error,
|
||||||
this.icon
|
this.icon
|
||||||
)
|
)
|
||||||
|
|
||||||
fun Source.toEntity(): SourceEntity =
|
fun SelfossModel.Source.toEntity(): SourceEntity =
|
||||||
SourceEntity(
|
SourceEntity(
|
||||||
this.id,
|
this.id,
|
||||||
this.getTitleDecoded(),
|
this.getTitleDecoded(),
|
||||||
this.tags.tags,
|
this.tags.joinToString(","),
|
||||||
this.spout,
|
this.spout,
|
||||||
this.error,
|
this.error,
|
||||||
this.icon.orEmpty()
|
this.icon
|
||||||
)
|
)
|
||||||
|
|
||||||
fun Tag.toEntity(): TagEntity =
|
fun SelfossModel.Tag.toEntity(): TagEntity =
|
||||||
TagEntity(
|
TagEntity(
|
||||||
this.tag,
|
this.tag,
|
||||||
this.color,
|
this.color,
|
||||||
this.unread
|
this.unread
|
||||||
)
|
)
|
||||||
|
|
||||||
fun ItemEntity.toView(): Item =
|
fun AndroidItemEntity.toView(): SelfossModel.Item =
|
||||||
Item(
|
SelfossModel.Item(
|
||||||
this.id,
|
this.id,
|
||||||
this.datetime,
|
this.datetime,
|
||||||
this.title,
|
this.title,
|
||||||
this.content,
|
this.content,
|
||||||
this.unread,
|
if (this.unread) 1 else 0,
|
||||||
this.starred,
|
if (this.starred) 1 else 0,
|
||||||
this.thumbnail,
|
this.thumbnail,
|
||||||
this.icon,
|
this.icon,
|
||||||
this.link,
|
this.link,
|
||||||
this.sourcetitle,
|
this.sourcetitle,
|
||||||
SelfossTagType(this.tags)
|
this.tags
|
||||||
)
|
)
|
||||||
|
|
||||||
fun Item.toEntity(): ItemEntity =
|
fun SelfossModel.Item.toEntity(): AndroidItemEntity =
|
||||||
ItemEntity(
|
AndroidItemEntity(
|
||||||
this.id,
|
this.id,
|
||||||
this.datetime,
|
this.datetime,
|
||||||
this.getTitleDecoded(),
|
this.getTitleDecoded(),
|
||||||
this.content,
|
this.content,
|
||||||
this.unread,
|
this.unread == 1,
|
||||||
this.starred,
|
this.starred == 1,
|
||||||
this.thumbnail,
|
this.thumbnail,
|
||||||
this.icon,
|
this.icon,
|
||||||
this.link,
|
this.link,
|
||||||
this.getSourceTitle(),
|
this.getSourceTitle(),
|
||||||
this.tags.tags
|
this.tags
|
||||||
)
|
)
|
@ -6,7 +6,7 @@ buildscript {
|
|||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10")
|
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10")
|
||||||
classpath("com.android.tools.build:gradle:7.1.2")
|
classpath("com.android.tools.build:gradle:7.2.0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
|||||||
#Wed Feb 09 17:05:19 CET 2022
|
#Wed Feb 09 17:05:19 CET 2022
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
@ -20,9 +20,12 @@ kotlin {
|
|||||||
sourceSets {
|
sourceSets {
|
||||||
val commonMain by getting {
|
val commonMain by getting {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("io.ktor:ktor-client-core:1.6.7")
|
implementation("io.ktor:ktor-client-core:2.0.1")
|
||||||
implementation("io.ktor:ktor-client-serialization:1.6.7")
|
implementation("io.ktor:ktor-client-content-negotiation:2.0.1")
|
||||||
|
implementation("io.ktor:ktor-serialization-kotlinx-json:2.0.1")
|
||||||
|
implementation("io.ktor:ktor-client-logging:2.0.1")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")
|
||||||
|
implementation("io.ktor:ktor-client-auth:2.0.1")
|
||||||
implementation("org.jsoup:jsoup:1.14.3")
|
implementation("org.jsoup:jsoup:1.14.3")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -34,7 +37,7 @@ kotlin {
|
|||||||
}
|
}
|
||||||
val androidMain by getting {
|
val androidMain by getting {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("io.ktor:ktor-client-android:1.6.7")
|
implementation("io.ktor:ktor-client-android:2.0.1")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val androidTest by getting {
|
val androidTest by getting {
|
||||||
@ -60,7 +63,7 @@ kotlin {
|
|||||||
iosX64Test.dependsOn(this)
|
iosX64Test.dependsOn(this)
|
||||||
iosArm64Test.dependsOn(this)
|
iosArm64Test.dependsOn(this)
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("io.ktor:ktor-client-ios:1.6.7")
|
implementation("io.ktor:ktor-client-ios:2.0.1")
|
||||||
}
|
}
|
||||||
//iosSimulatorArm64Test.dependsOn(this)
|
//iosSimulatorArm64Test.dependsOn(this)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.dao
|
||||||
|
|
||||||
|
interface DeviceDatabase<ItemEntity> {
|
||||||
|
suspend fun items(): List<ItemEntity>
|
||||||
|
suspend fun insertAllItems(vararg items: ItemEntity)
|
||||||
|
suspend fun deleteAllItems()
|
||||||
|
suspend fun delete(item: ItemEntity)
|
||||||
|
suspend fun updateItem(item: ItemEntity)
|
||||||
|
}
|
@ -1,188 +1,229 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.rest
|
package bou.amine.apps.readerforselfossv2.rest
|
||||||
|
|
||||||
import android.content.Context
|
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
|
||||||
import android.os.Parcel
|
|
||||||
import android.os.Parcelable
|
|
||||||
import android.text.Html
|
|
||||||
import bou.amine.apps.readerforselfossv2.rest.SelfossModel.SelfossModel.constructUrl
|
|
||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
import io.ktor.client.features.*
|
import io.ktor.client.call.*
|
||||||
import io.ktor.client.features.json.*
|
import io.ktor.client.engine.*
|
||||||
import io.ktor.client.features.json.serializer.*
|
import io.ktor.client.engine.ProxyBuilder.http
|
||||||
|
import io.ktor.client.plugins.auth.*
|
||||||
|
import io.ktor.client.plugins.auth.providers.*
|
||||||
import io.ktor.client.request.*
|
import io.ktor.client.request.*
|
||||||
import io.ktor.client.request.forms.*
|
import io.ktor.client.request.forms.*
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
import kotlinx.coroutines.coroutineScope
|
import io.ktor.client.plugins.contentnegotiation.*
|
||||||
import kotlinx.coroutines.launch
|
import io.ktor.client.plugins.logging.*
|
||||||
import kotlinx.serialization.Serializable
|
import io.ktor.serialization.kotlinx.json.*
|
||||||
import kotlin.jvm.JvmField
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
class SelfossApi {
|
class SelfossApi(private val apiDetailsService: ApiDetailsService) {
|
||||||
/**
|
|
||||||
* TODO:
|
|
||||||
* Self signed certs
|
|
||||||
* Timeout + 408
|
|
||||||
* Auth digest/basic
|
|
||||||
* Loging
|
|
||||||
*/
|
|
||||||
|
|
||||||
val baseUrl = "http://10.0.2.2:8888"
|
private val client = HttpClient() {
|
||||||
val userName = ""
|
install(ContentNegotiation) {
|
||||||
val password = ""
|
json(Json {
|
||||||
val client = HttpClient() {
|
|
||||||
install(JsonFeature) {
|
|
||||||
serializer = KotlinxSerializer(kotlinx.serialization.json.Json {
|
|
||||||
prettyPrint = true
|
prettyPrint = true
|
||||||
isLenient = true
|
isLenient = true
|
||||||
ignoreUnknownKeys = true
|
ignoreUnknownKeys = true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
install(Logging) {
|
||||||
|
logger = object: Logger {
|
||||||
|
override fun log(message: String) {
|
||||||
|
apiDetailsService.logApiCalls(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
level = LogLevel.ALL
|
||||||
|
}
|
||||||
|
/* TODO: Auth as basic
|
||||||
|
if (apiDetailsService.getUserName().isNotEmpty() && apiDetailsService.getPassword().isNotEmpty()) {
|
||||||
|
|
||||||
|
install(Auth) {
|
||||||
|
basic {
|
||||||
|
credentials {
|
||||||
|
BasicAuthCredentials(username = apiDetailsService.getUserName(), password = apiDetailsService.getPassword())
|
||||||
|
}
|
||||||
|
sendWithoutRequest {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
expectSuccess = false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun url(path: String) =
|
private fun url(path: String) =
|
||||||
"$baseUrl$path"
|
"${apiDetailsService.getBaseUrl()}$path"
|
||||||
|
|
||||||
|
|
||||||
suspend fun login() =
|
suspend fun login(): SelfossModel.SuccessResponse? =
|
||||||
client.get<String>(url("/login"))// Todo: params
|
client.get(url("/login")) {
|
||||||
|
parameter("username", apiDetailsService.getUserName())
|
||||||
|
parameter("password", apiDetailsService.getPassword())
|
||||||
|
}.body()
|
||||||
|
|
||||||
suspend fun getItems(type: String,
|
suspend fun getItems(
|
||||||
items: Int,
|
type: String,
|
||||||
offset: Int,
|
items: Int,
|
||||||
tag: String? = "",
|
offset: Int,
|
||||||
source: Long? = null,
|
tag: String? = "",
|
||||||
search: String? = "",
|
source: Long? = null,
|
||||||
updatedSince: String? = ""): List<SelfossModel.Item> =
|
search: String? = "",
|
||||||
|
updatedSince: String? = ""
|
||||||
|
): List<SelfossModel.Item>? =
|
||||||
client.get(url("/items")) {
|
client.get(url("/items")) {
|
||||||
parameter("username", userName)
|
parameter("username", apiDetailsService.getUserName())
|
||||||
parameter("password", password)
|
parameter("password", apiDetailsService.getPassword())
|
||||||
parameter("type", type)
|
parameter("type", type)
|
||||||
parameter("tag", tag)
|
parameter("tag", tag)
|
||||||
parameter("source", source)
|
parameter("source", source)
|
||||||
parameter("search", search)
|
parameter("search", search)
|
||||||
parameter("updatedsince", updatedSince)
|
parameter("updatedsince", updatedSince)
|
||||||
parameter("items", items)
|
parameter("items", items)
|
||||||
parameter("offset", offset)
|
parameter("offset", offset)
|
||||||
}
|
}.body()
|
||||||
|
|
||||||
suspend fun stats(): SelfossModel.Stats =
|
suspend fun stats(): SelfossModel.Stats? =
|
||||||
client.get(url("/stats")) {
|
client.get(url("/stats")) {
|
||||||
parameter("username", userName)
|
parameter("username", apiDetailsService.getUserName())
|
||||||
parameter("password", password)
|
parameter("password", apiDetailsService.getPassword())
|
||||||
}
|
}.body()
|
||||||
|
|
||||||
suspend fun tags(): List<SelfossModel.Tag> =
|
suspend fun tags(): List<SelfossModel.Tag>? =
|
||||||
client.get(url("/tags")) {
|
client.get(url("/tags")) {
|
||||||
parameter("username", userName)
|
parameter("username", apiDetailsService.getUserName())
|
||||||
parameter("password", password)
|
parameter("password", apiDetailsService.getPassword())
|
||||||
}
|
}.body()
|
||||||
|
|
||||||
suspend fun update(): String =
|
suspend fun update(): SelfossModel.SuccessResponse? =
|
||||||
client.get(url("/update")) {
|
client.get(url("/update")) {
|
||||||
parameter("username", userName)
|
parameter("username", apiDetailsService.getUserName())
|
||||||
parameter("password", password)
|
parameter("password", apiDetailsService.getPassword())
|
||||||
}
|
}.body()
|
||||||
|
|
||||||
suspend fun spouts(): Map<String, SelfossModel.Spout> =
|
suspend fun spouts(): Map<String, SelfossModel.Spout>? =
|
||||||
client.get(url("/sources/spouts")) {
|
client.get(url("/a/spouts")) {
|
||||||
parameter("username", userName)
|
parameter("username", apiDetailsService.getUserName())
|
||||||
parameter("password", password)
|
parameter("password", apiDetailsService.getPassword())
|
||||||
}
|
}.body()
|
||||||
|
|
||||||
suspend fun sources(): List<SelfossModel.Source> =
|
suspend fun sources(): ArrayList<SelfossModel.Source>? =
|
||||||
client.get(url("/sources/list")) {
|
client.get(url("/sources/list")) {
|
||||||
parameter("username", userName)
|
parameter("username", apiDetailsService.getUserName())
|
||||||
parameter("password", password)
|
parameter("password", apiDetailsService.getPassword())
|
||||||
|
}.body()
|
||||||
|
|
||||||
|
suspend fun version(): SelfossModel.ApiVersion? =
|
||||||
|
client.get(url("/api/about")).body()
|
||||||
|
|
||||||
|
suspend fun markAsRead(id: String): SelfossModel.SuccessResponse? =
|
||||||
|
client.submitForm(
|
||||||
|
url = url("/mark/$id"),
|
||||||
|
formParameters = Parameters.build {
|
||||||
|
append("username", apiDetailsService.getUserName())
|
||||||
|
append("password", apiDetailsService.getPassword())
|
||||||
|
},
|
||||||
|
encodeInQuery = true
|
||||||
|
).body()
|
||||||
|
|
||||||
|
suspend fun unmarkAsRead(id: String): SelfossModel.SuccessResponse? =
|
||||||
|
client.submitForm(
|
||||||
|
url = url("/unmark/$id"),
|
||||||
|
formParameters = Parameters.build {
|
||||||
|
append("username", apiDetailsService.getUserName())
|
||||||
|
append("password", apiDetailsService.getPassword())
|
||||||
|
},
|
||||||
|
encodeInQuery = true
|
||||||
|
).body()
|
||||||
|
|
||||||
|
suspend fun starr(id: String): SelfossModel.SuccessResponse? =
|
||||||
|
client.submitForm(
|
||||||
|
url = url("/starr/$id"),
|
||||||
|
formParameters = Parameters.build {
|
||||||
|
append("username", apiDetailsService.getUserName())
|
||||||
|
append("password", apiDetailsService.getPassword())
|
||||||
|
},
|
||||||
|
encodeInQuery = true
|
||||||
|
).body()
|
||||||
|
|
||||||
|
suspend fun unstarr(id: String): SelfossModel.SuccessResponse? =
|
||||||
|
client.submitForm(
|
||||||
|
url = url("/unstarr/$id"),
|
||||||
|
formParameters = Parameters.build {
|
||||||
|
append("username", apiDetailsService.getUserName())
|
||||||
|
append("password", apiDetailsService.getPassword())
|
||||||
|
},
|
||||||
|
encodeInQuery = true
|
||||||
|
).body()
|
||||||
|
|
||||||
|
suspend fun markAllAsRead(ids: List<String>): SelfossModel.SuccessResponse? =
|
||||||
|
client.submitForm(
|
||||||
|
url = url("/mark"),
|
||||||
|
formParameters = Parameters.build {
|
||||||
|
append("username", apiDetailsService.getUserName())
|
||||||
|
append("password", apiDetailsService.getPassword())
|
||||||
|
append("ids[]", ids.joinToString(","))
|
||||||
|
},
|
||||||
|
encodeInQuery = true
|
||||||
|
).body()
|
||||||
|
|
||||||
|
suspend fun createSourceForVersion(
|
||||||
|
title: String,
|
||||||
|
url: String,
|
||||||
|
spout: String,
|
||||||
|
tags: String,
|
||||||
|
filter: String,
|
||||||
|
version: Int
|
||||||
|
): SelfossModel.SuccessResponse? =
|
||||||
|
if (version > 1) {
|
||||||
|
createSource(title, url, spout, tags, filter)
|
||||||
|
} else {
|
||||||
|
createSource2(title, url, spout, tags, filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun version(): SelfossModel.ApiVersion =
|
private suspend fun createSource(
|
||||||
client.get(url("/api/about"))
|
title: String,
|
||||||
|
url: String,
|
||||||
suspend fun markAsRead(id: String): SelfossModel.SuccessResponse =
|
spout: String,
|
||||||
|
tags: String,
|
||||||
|
filter: String
|
||||||
|
): SelfossModel.SuccessResponse? =
|
||||||
client.submitForm(
|
client.submitForm(
|
||||||
url = url("/mark/$id"),
|
url = url("/source"),
|
||||||
formParameters = Parameters.build {
|
formParameters = Parameters.build {
|
||||||
append("username", userName)
|
append("username", apiDetailsService.getUserName())
|
||||||
append("password", password)
|
append("password", apiDetailsService.getPassword())
|
||||||
},
|
append("title", title)
|
||||||
encodeInQuery = true
|
append("url", url)
|
||||||
)
|
append("spout", spout)
|
||||||
|
append("tags", tags)
|
||||||
|
append("filter", filter)
|
||||||
|
},
|
||||||
|
encodeInQuery = true
|
||||||
|
).body()
|
||||||
|
|
||||||
suspend fun unmarkAsRead(id: String): SelfossModel.SuccessResponse =
|
private suspend fun createSource2(
|
||||||
|
title: String,
|
||||||
|
url: String,
|
||||||
|
spout: String,
|
||||||
|
tags: String,
|
||||||
|
filter: String
|
||||||
|
): SelfossModel.SuccessResponse? =
|
||||||
client.submitForm(
|
client.submitForm(
|
||||||
url = url("/unmark/$id"),
|
url = url("/source"),
|
||||||
formParameters = Parameters.build {
|
formParameters = Parameters.build {
|
||||||
append("username", userName)
|
append("username", apiDetailsService.getUserName())
|
||||||
append("password", password)
|
append("password", apiDetailsService.getPassword())
|
||||||
},
|
append("title", title)
|
||||||
encodeInQuery = true
|
append("url", url)
|
||||||
)
|
append("spout", spout)
|
||||||
|
append("tags[]", tags)
|
||||||
|
append("filter", filter)
|
||||||
|
},
|
||||||
|
encodeInQuery = true
|
||||||
|
).body()
|
||||||
|
|
||||||
suspend fun starr(id: String): SelfossModel.SuccessResponse =
|
suspend fun deleteSource(id: String): SelfossModel.SuccessResponse? =
|
||||||
client.submitForm(
|
client.delete(url("/source/$id")) {
|
||||||
url = url("/starr/$id"),
|
parameter("username", apiDetailsService.getUserName())
|
||||||
formParameters = Parameters.build {
|
parameter("password", apiDetailsService.getPassword())
|
||||||
append("username", userName)
|
}.body()
|
||||||
append("password", password)
|
|
||||||
},
|
|
||||||
encodeInQuery = true
|
|
||||||
)
|
|
||||||
|
|
||||||
suspend fun unstarr(id: String): SelfossModel.SuccessResponse =
|
|
||||||
client.submitForm(
|
|
||||||
url = url("/unstarr/$id"),
|
|
||||||
formParameters = Parameters.build {
|
|
||||||
append("username", userName)
|
|
||||||
append("password", password)
|
|
||||||
},
|
|
||||||
encodeInQuery = true
|
|
||||||
)
|
|
||||||
|
|
||||||
suspend fun markAllAsRead(ids: List<String>): SelfossModel.SuccessResponse =
|
|
||||||
client.submitForm(
|
|
||||||
url = url("/mark"),
|
|
||||||
formParameters = Parameters.build {
|
|
||||||
append("username", userName)
|
|
||||||
append("password", password)
|
|
||||||
append("ids[]", ids.joinToString(","))
|
|
||||||
},
|
|
||||||
encodeInQuery = true
|
|
||||||
)
|
|
||||||
|
|
||||||
suspend fun createSource(title: String, url: String, spout: String, tags: String, filter: String): SelfossModel.SuccessResponse =
|
|
||||||
client.submitForm(
|
|
||||||
url = url("/source"),
|
|
||||||
formParameters = Parameters.build {
|
|
||||||
append("username", userName)
|
|
||||||
append("password", password)
|
|
||||||
append("title", title)
|
|
||||||
append("url", url)
|
|
||||||
append("spout", spout)
|
|
||||||
append("tags", tags)
|
|
||||||
append("filter", filter)
|
|
||||||
},
|
|
||||||
encodeInQuery = true
|
|
||||||
)
|
|
||||||
|
|
||||||
suspend fun createSource2(title: String, url: String, spout: String, tags: String, filter: String): SelfossModel.SuccessResponse =
|
|
||||||
client.submitForm(
|
|
||||||
url = url("/source"),
|
|
||||||
formParameters = Parameters.build {
|
|
||||||
append("username", userName)
|
|
||||||
append("password", password)
|
|
||||||
append("title", title)
|
|
||||||
append("url", url)
|
|
||||||
append("spout", spout)
|
|
||||||
append("tags[]", tags)
|
|
||||||
append("filter", filter)
|
|
||||||
},
|
|
||||||
encodeInQuery = true
|
|
||||||
)
|
|
||||||
|
|
||||||
suspend fun deleteSource(id: String) =
|
|
||||||
client.delete<SelfossModel.SuccessResponse>(url("/source/$id")) {
|
|
||||||
parameter("username", userName)
|
|
||||||
parameter("password", password)
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,15 +1,8 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.rest
|
package bou.amine.apps.readerforselfossv2.rest
|
||||||
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Parcel
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.text.Html
|
import android.text.Html
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import java.util.*
|
|
||||||
import kotlin.collections.ArrayList
|
|
||||||
import kotlin.jvm.JvmField
|
|
||||||
import org.jsoup.Jsoup
|
|
||||||
import java.util.Locale.US
|
|
||||||
|
|
||||||
class SelfossModel {
|
class SelfossModel {
|
||||||
|
|
||||||
@ -61,19 +54,11 @@ class SelfossModel {
|
|||||||
data class Source(
|
data class Source(
|
||||||
val id: String,
|
val id: String,
|
||||||
val title: String,
|
val title: String,
|
||||||
val tags: String,
|
val tags: List<String>,
|
||||||
val spout: String,
|
val spout: String,
|
||||||
val error: String,
|
val error: String,
|
||||||
val icon: String
|
val icon: String
|
||||||
) {
|
)
|
||||||
fun getIcon(baseUrl: String): String {
|
|
||||||
return constructUrl(baseUrl, "favicons", icon)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getTitleDecoded(): String {
|
|
||||||
return Html.fromHtml(title).toString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Item(
|
data class Item(
|
||||||
@ -88,81 +73,5 @@ class SelfossModel {
|
|||||||
val link: String,
|
val link: String,
|
||||||
val sourcetitle: String,
|
val sourcetitle: String,
|
||||||
val tags: String
|
val tags: String
|
||||||
) {
|
)
|
||||||
|
|
||||||
fun getIcon(baseUrl: String): String {
|
|
||||||
return constructUrl(baseUrl, "favicons", icon)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getThumbnail(baseUrl: String): String {
|
|
||||||
return constructUrl(baseUrl, "thumbnails", thumbnail)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getImages() : ArrayList<String> {
|
|
||||||
val allImages = ArrayList<String>()
|
|
||||||
|
|
||||||
for ( image in Jsoup.parse(content).getElementsByTag("img")) {
|
|
||||||
val url = image.attr("src")
|
|
||||||
if (url.lowercase(US).contains(".jpg") ||
|
|
||||||
url.lowercase(US).contains(".jpeg") ||
|
|
||||||
url.lowercase(US).contains(".png") ||
|
|
||||||
url.lowercase(US).contains(".webp"))
|
|
||||||
{
|
|
||||||
allImages.add(url)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return allImages
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getTitleDecoded(): String {
|
|
||||||
return Html.fromHtml(title).toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getSourceTitle(): String {
|
|
||||||
return Html.fromHtml(sourcetitle).toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: maybe find a better way to handle these kind of urls
|
|
||||||
fun getLinkDecoded(): String {
|
|
||||||
var stringUrl: String
|
|
||||||
stringUrl =
|
|
||||||
if (link.startsWith("http://news.google.com/news/") || link.startsWith("https://news.google.com/news/")) {
|
|
||||||
if (link.contains("&url=")) {
|
|
||||||
link.substringAfter("&url=")
|
|
||||||
} else {
|
|
||||||
this.link.replace("&", "&")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.link.replace("&", "&")
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle :443 => https
|
|
||||||
if (stringUrl.contains(":443")) {
|
|
||||||
stringUrl = stringUrl.replace(":443", "").replace("http://", "https://")
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle url not starting with http
|
|
||||||
if (stringUrl.startsWith("//")) {
|
|
||||||
stringUrl = "http:$stringUrl"
|
|
||||||
}
|
|
||||||
|
|
||||||
return stringUrl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object SelfossModel {
|
|
||||||
private fun String?.isEmptyOrNullOrNullString(): Boolean =
|
|
||||||
this == null || this == "null" || this.isEmpty()
|
|
||||||
|
|
||||||
fun constructUrl(baseUrl: String, path: String, file: String?): String {
|
|
||||||
return if (file.isEmptyOrNullOrNullString()) {
|
|
||||||
""
|
|
||||||
} else {
|
|
||||||
val baseUriBuilder = Uri.parse(baseUrl).buildUpon()
|
|
||||||
baseUriBuilder.appendPath(path).appendPath(file)
|
|
||||||
|
|
||||||
baseUriBuilder.toString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.service
|
||||||
|
|
||||||
|
interface ApiDetailsService {
|
||||||
|
fun logApiCalls(message: String)
|
||||||
|
fun getApiVersion(): Int
|
||||||
|
fun getBaseUrl(): String
|
||||||
|
fun getUserName(): String
|
||||||
|
fun getPassword(): String
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.service
|
||||||
|
|
||||||
|
import bou.amine.apps.readerforselfossv2.dao.DeviceDatabase
|
||||||
|
import bou.amine.apps.readerforselfossv2.utils.parseDate
|
||||||
|
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||||
|
|
||||||
|
abstract class DeviceDataBaseService<ItemEntity>(val db: DeviceDatabase<ItemEntity>, private val searchService: SearchService) {
|
||||||
|
var itemsCaching = false
|
||||||
|
var items: ArrayList<SelfossModel.Item> = arrayListOf()
|
||||||
|
get() {
|
||||||
|
return ArrayList(field)
|
||||||
|
}
|
||||||
|
set(value) {
|
||||||
|
field = ArrayList(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract suspend fun updateDatabase()
|
||||||
|
abstract suspend fun clearDBItems()
|
||||||
|
abstract fun appendNewItems(items: List<SelfossModel.Item>)
|
||||||
|
abstract fun getFromDB()
|
||||||
|
|
||||||
|
fun sortItems() {
|
||||||
|
val tmpItems = ArrayList(items.sortedByDescending { it.parseDate(searchService.dateUtils) })
|
||||||
|
items = tmpItems
|
||||||
|
}
|
||||||
|
|
||||||
|
// This filtered items from items val. Do not use
|
||||||
|
fun getFocusedItems() {}
|
||||||
|
fun computeBadges() {
|
||||||
|
searchService.badgeUnread = items.filter { item -> item.unread == 1 }.size
|
||||||
|
searchService.badgeStarred = items.filter { item -> item.starred == 1 }.size
|
||||||
|
searchService.badgeAll = items.size
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.service
|
||||||
|
|
||||||
|
import bou.amine.apps.readerforselfossv2.utils.DateUtils
|
||||||
|
|
||||||
|
class SearchService(val dateUtils: DateUtils) {
|
||||||
|
var displayedItems: String = "unread"
|
||||||
|
set(value) {
|
||||||
|
field = when (value) {
|
||||||
|
"all" -> "all"
|
||||||
|
"unread" -> "unread"
|
||||||
|
"read" -> "read"
|
||||||
|
"starred" -> "starred"
|
||||||
|
else -> "all"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var position = 0
|
||||||
|
var searchFilter: String? = null
|
||||||
|
var sourceIDFilter: Long? = null
|
||||||
|
var sourceFilter: String? = null
|
||||||
|
var tagFilter: String? = null
|
||||||
|
var itemsCaching = false
|
||||||
|
|
||||||
|
var fetchedUnread = false
|
||||||
|
var fetchedAll = false
|
||||||
|
var fetchedStarred = false
|
||||||
|
|
||||||
|
var badgeUnread = -1
|
||||||
|
var badgeAll = -1
|
||||||
|
var badgeStarred = -1
|
||||||
|
}
|
@ -0,0 +1,152 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.service
|
||||||
|
|
||||||
|
|
||||||
|
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
|
||||||
|
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
|
||||||
|
class SelfossService<ItemEntity>(val api: SelfossApi, private val dbService: DeviceDataBaseService<ItemEntity>, private val searchService: SearchService) {
|
||||||
|
|
||||||
|
suspend fun getAndStoreAllItems(isNetworkAvailable: Boolean) = withContext(
|
||||||
|
Dispatchers.Default) {
|
||||||
|
if (isNetworkAvailable) {
|
||||||
|
launch {
|
||||||
|
try {
|
||||||
|
enqueueArticles(allNewItems(), true)
|
||||||
|
} catch (e: Throwable) {}
|
||||||
|
}
|
||||||
|
launch {
|
||||||
|
try {
|
||||||
|
enqueueArticles(allReadItems(), false)
|
||||||
|
} catch (e: Throwable) {}
|
||||||
|
}
|
||||||
|
launch {
|
||||||
|
try {
|
||||||
|
enqueueArticles(allStarredItems(), false)
|
||||||
|
} catch (e: Throwable) {}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
launch { dbService.updateDatabase() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun refreshFocusedItems(itemsNumber: Int, isNetworkAvailable: Boolean) = withContext(
|
||||||
|
Dispatchers.Default) {
|
||||||
|
if (isNetworkAvailable) {
|
||||||
|
val response = when (searchService.displayedItems) {
|
||||||
|
"read" -> readItems(itemsNumber, 0)
|
||||||
|
"unread" -> newItems(itemsNumber, 0)
|
||||||
|
"starred" -> starredItems(itemsNumber, 0)
|
||||||
|
else -> readItems(itemsNumber, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response != null) {
|
||||||
|
// TODO:
|
||||||
|
// dbService.refreshFocusedItems(response.body() as ArrayList<SelfossModel.Item>)
|
||||||
|
dbService.updateDatabase()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getReadItems(itemsNumber: Int, offset: Int, isNetworkAvailable: Boolean) = withContext(
|
||||||
|
Dispatchers.Default) {
|
||||||
|
if (isNetworkAvailable) {
|
||||||
|
try {
|
||||||
|
enqueueArticles(readItems( itemsNumber, offset), false)
|
||||||
|
searchService.fetchedAll = true
|
||||||
|
dbService.updateDatabase()
|
||||||
|
} catch (e: Throwable) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getUnreadItems(itemsNumber: Int, offset: Int, isNetworkAvailable: Boolean) = withContext(
|
||||||
|
Dispatchers.Default) {
|
||||||
|
if (isNetworkAvailable) {
|
||||||
|
try {
|
||||||
|
if (!searchService.fetchedUnread) {
|
||||||
|
dbService.clearDBItems()
|
||||||
|
}
|
||||||
|
enqueueArticles(newItems(itemsNumber, offset), false)
|
||||||
|
searchService.fetchedUnread = true
|
||||||
|
} catch (e: Throwable) {}
|
||||||
|
}
|
||||||
|
dbService.updateDatabase()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getStarredItems(itemsNumber: Int, offset: Int, isNetworkAvailable: Boolean) = withContext(
|
||||||
|
Dispatchers.Default) {
|
||||||
|
if (isNetworkAvailable) {
|
||||||
|
try {
|
||||||
|
enqueueArticles(starredItems(itemsNumber, offset), false)
|
||||||
|
searchService.fetchedStarred = true
|
||||||
|
dbService.updateDatabase()
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun readAll(isNetworkAvailable: Boolean): Boolean {
|
||||||
|
var success = false
|
||||||
|
if (isNetworkAvailable) {
|
||||||
|
// Do api call to read all
|
||||||
|
} else {
|
||||||
|
// Do db call to read all
|
||||||
|
}
|
||||||
|
// refresh view
|
||||||
|
return success
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun reloadBadges(isNetworkAvailable: Boolean) = withContext(Dispatchers.Default) {
|
||||||
|
if (isNetworkAvailable) {
|
||||||
|
try {
|
||||||
|
val response = api.stats()
|
||||||
|
|
||||||
|
if (response != null) {
|
||||||
|
searchService.badgeUnread = response.unread
|
||||||
|
searchService.badgeAll = response.total
|
||||||
|
searchService.badgeStarred = response.starred
|
||||||
|
}
|
||||||
|
} catch (e: Throwable) {}
|
||||||
|
} else {
|
||||||
|
dbService.computeBadges()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun enqueueArticles(response: List<SelfossModel.Item>?, clearDatabase: Boolean) {
|
||||||
|
if (response != null) {
|
||||||
|
if (clearDatabase) {
|
||||||
|
CoroutineScope(Dispatchers.Default).launch {
|
||||||
|
dbService.clearDBItems()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dbService.appendNewItems(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun allNewItems(): List<SelfossModel.Item>? =
|
||||||
|
readItems(200, 0)
|
||||||
|
|
||||||
|
private suspend fun allReadItems(): List<SelfossModel.Item>? =
|
||||||
|
newItems(200, 0)
|
||||||
|
|
||||||
|
private suspend fun allStarredItems(): List<SelfossModel.Item>? =
|
||||||
|
starredItems(200, 0)
|
||||||
|
|
||||||
|
private suspend fun readItems(
|
||||||
|
itemsNumber: Int,
|
||||||
|
offset: Int
|
||||||
|
): List<SelfossModel.Item>? =
|
||||||
|
api.getItems("read", itemsNumber, offset, searchService.tagFilter, searchService.sourceIDFilter, searchService.searchFilter)
|
||||||
|
|
||||||
|
private suspend fun newItems(
|
||||||
|
itemsNumber: Int,
|
||||||
|
offset: Int
|
||||||
|
): List<SelfossModel.Item>? =
|
||||||
|
api.getItems("unread", itemsNumber, offset, searchService.tagFilter, searchService.sourceIDFilter, searchService.searchFilter)
|
||||||
|
|
||||||
|
private suspend fun starredItems(
|
||||||
|
itemsNumber: Int,
|
||||||
|
offset: Int
|
||||||
|
): List<SelfossModel.Item>? =
|
||||||
|
api.getItems("starred", itemsNumber, offset, searchService.tagFilter, searchService.sourceIDFilter, searchService.searchFilter)
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.utils
|
||||||
|
|
||||||
|
import android.text.format.DateUtils
|
||||||
|
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||||
|
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
|
||||||
|
|
||||||
|
fun SelfossModel.Item.parseDate(dateUtils: bou.amine.apps.readerforselfossv2.utils.DateUtils): Instant =
|
||||||
|
dateUtils.parseDate(this.datetime)
|
||||||
|
|
||||||
|
fun SelfossModel.Item.parseRelativeDate(dateUtils: bou.amine.apps.readerforselfossv2.utils.DateUtils): String =
|
||||||
|
dateUtils.parseRelativeDate(this.datetime)
|
||||||
|
|
||||||
|
class DateUtils(private val apiDetailsService: ApiDetailsService) {
|
||||||
|
fun parseDate(dateString: String): Instant {
|
||||||
|
|
||||||
|
val FORMATTERV1 = "yyyy-MM-dd HH:mm:ss"
|
||||||
|
|
||||||
|
return if (apiDetailsService.getApiVersion() >= 4) {
|
||||||
|
OffsetDateTime.parse(dateString).toInstant()
|
||||||
|
} else {
|
||||||
|
LocalDateTime.parse(dateString, DateTimeFormatter.ofPattern(FORMATTERV1)).toInstant(ZoneOffset.UTC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseRelativeDate(dateString: String): String {
|
||||||
|
|
||||||
|
val date = parseDate(dateString)
|
||||||
|
|
||||||
|
return " " + DateUtils.getRelativeTimeSpanString(
|
||||||
|
date.toEpochMilli(),
|
||||||
|
Instant.now().toEpochMilli(),
|
||||||
|
60000L, // DateUtils.MINUTE_IN_MILLIS,
|
||||||
|
262144 // DateUtils.FORMAT_ABBREV_RELATIVE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.utils
|
||||||
|
|
||||||
|
fun String?.isEmptyOrNullOrNullString(): Boolean =
|
||||||
|
this == null || this == "null" || this.isEmpty()
|
||||||
|
|
||||||
|
fun String.longHash(): Long {
|
||||||
|
var h = 98764321261L
|
||||||
|
val l = this.length
|
||||||
|
val chars = this.toCharArray()
|
||||||
|
|
||||||
|
for (i in 0 until l) {
|
||||||
|
h = 31 * h + chars[i].code.toLong()
|
||||||
|
}
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String.toStringUriWithHttp(): String =
|
||||||
|
if (!this.startsWith("https://") && !this.startsWith("http://")) {
|
||||||
|
"http://" + this
|
||||||
|
} else {
|
||||||
|
this
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user