Compare commits
1 Commits
v123051301
...
binnette-p
Author | SHA1 | Date | |
---|---|---|---|
d0f0501bf3 |
21
.drone.yml
21
.drone.yml
@ -137,27 +137,6 @@ steps:
|
||||
from_secret: giteaAPI
|
||||
base_url: https://gitea.amine-louveau.fr
|
||||
files: signed.apk
|
||||
|
||||
- name: notify
|
||||
image: drillster/drone-email
|
||||
failure: ignore
|
||||
settings:
|
||||
host:
|
||||
from_secret: smtpHOST
|
||||
port:
|
||||
from_secret: smtpPORT
|
||||
username:
|
||||
from_secret: smtpUSERNAME
|
||||
password:
|
||||
from_secret: smtpPASSWORD
|
||||
from:
|
||||
from_secret: smtpFROM
|
||||
subject: Mapping file
|
||||
recipients:
|
||||
from_secret: smtpTO
|
||||
recipients_only: true
|
||||
skip_verify: true
|
||||
attachment: androidApp/build/outputs/mapping/githubConfigRelease/mapping.txt
|
||||
trigger:
|
||||
event:
|
||||
- tag
|
65
CHANGELOG.md
65
CHANGELOG.md
@ -1,68 +1,3 @@
|
||||
**v123051211**
|
||||
|
||||
- fix: Sometimes url isn't even defined.
|
||||
- Changelog for v123041021 [CI SKIP]
|
||||
|
||||
--------------------------------------------------------------------
|
||||
|
||||
**v123041021**
|
||||
|
||||
- fix: 'Enable Core Library Desugaring to support older Android versions' (#138) from davidoskky/ReaderForSelfoss-multiplatform:desugaring into master
|
||||
- Enable Core Library Desugaring to support older Android versions
|
||||
- Changelog for v123030851 [CI SKIP]
|
||||
|
||||
--------------------------------------------------------------------
|
||||
|
||||
**v123030851**
|
||||
|
||||
- chore: replace textDrawable library (#136)
|
||||
- refactor: Remove slow login check. Closes #135.
|
||||
- ci: send the mapping file after a release.
|
||||
- Changelog for v123030751 [CI SKIP]
|
||||
|
||||
--------------------------------------------------------------------
|
||||
|
||||
**v123030751**
|
||||
|
||||
- debug: added a lot to pinpoint the url issue.
|
||||
- feat: Use /sources/stats in the home (#133)
|
||||
- Changelog for v123030681 [CI SKIP]
|
||||
|
||||
--------------------------------------------------------------------
|
||||
|
||||
**v123030681**
|
||||
|
||||
- fix: Unread and starred can be null.
|
||||
- Fixed version number issue.
|
||||
- Changelog for v123030621 [CI SKIP]
|
||||
|
||||
--------------------------------------------------------------------
|
||||
|
||||
**v123030621**
|
||||
|
||||
- fix: url required issue.
|
||||
- fix: Canvas reused issue.
|
||||
- Changelog for v123020572 [CI SKIP]
|
||||
|
||||
--------------------------------------------------------------------
|
||||
|
||||
**v123020572**
|
||||
|
||||
- fix: requirecontext issues ?
|
||||
- debug: activity not found exception.
|
||||
- Changelog for v123020571 [CI SKIP]
|
||||
|
||||
--------------------------------------------------------------------
|
||||
|
||||
**v123020571**
|
||||
|
||||
- chore: remove errors logging.
|
||||
- fix: quickfix for url param not provided for some sources.
|
||||
- Update 'CHANGELOG.md'
|
||||
- Changelog for v123020523 [CI SKIP]
|
||||
|
||||
--------------------------------------------------------------------
|
||||
|
||||
**v123020523**
|
||||
|
||||
- fix: Git changelog.
|
||||
|
14
README.md
14
README.md
@ -2,11 +2,11 @@
|
||||
|
||||
[](https://crowdin.com/project/readerforselfoss)
|
||||
|
||||
It's an RSS Reader for Android and IOS, that **only** works with [Selfoss](https://selfoss.aditu.de/)
|
||||
**ReaderForSelfoss** is a RSS Reader for Android and IOS, that **only** works with [Selfoss](https://selfoss.aditu.de/)
|
||||
|
||||
If you are a developer, don't hesitate to help with PRs.
|
||||
|
||||
If you are a user, you can still create new issues. I'll fix them when I can.
|
||||
If you are a user, fell free to create issues. I'll fix them when I can.
|
||||
|
||||
<a href="https://f-droid.org/packages/bou.amine.apps.readerforselfossv2.android"><img src="https://f-droid.org/badge/get-it-on.png" alt="Get it on F-Droid" height="100"></a>
|
||||
|
||||
@ -14,13 +14,13 @@ If you are a user, you can still create new issues. I'll fix them when I can.
|
||||
|
||||
<img src="res//fr-card.png?raw=true" alt="card view" width="400"/> <img src="res//fr-list.png?raw=true" alt="list view" width="400"/>
|
||||
|
||||
## Like my app ?
|
||||
## Like my app?
|
||||
|
||||
<a href="https://www.buymeacoffee.com/aminecmi" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/lato-orange.png" alt="Buy Me A Coffee" style="height: 51px !important;width: 217px !important;" ></a>
|
||||
|
||||
## Want to help ?
|
||||
## Want to help?
|
||||
|
||||
1. **You'll have to have a Selfoss instance running.** You'll find everything you need to install it [here](https://selfoss.aditu.de/).
|
||||
1. **You need a Selfoss instance running.** Find everything you need to install it [here](https://selfoss.aditu.de/).
|
||||
|
||||
2. Check the [Contribution guide](https://gitea.amine-louveau.fr/Louvorg/ReaderforSelfoss-multiplatform/src/branch/master/.github/CONTRIBUTING.md).
|
||||
|
||||
@ -30,8 +30,8 @@ If you are a user, you can still create new issues. I'll fix them when I can.
|
||||
|
||||
- [Check what changed](https://gitea.amine-louveau.fr/Louvorg/ReaderforSelfoss-multiplatform/src/branch/master/CHANGELOG.md)
|
||||
- [See what I'm doing](https://gitea.amine-louveau.fr/Louvorg/ReaderforSelfoss-multiplatform/projects/1)
|
||||
- [Create an issue, or request a new feature](https://gitea.amine-louveau.fr/Louvorg/ReaderforSelfoss-multiplatform/issues)
|
||||
- [Help translation the app](https://crowdin.com/project/readerforselfoss)
|
||||
- [Create an issue or request a new feature](https://gitea.amine-louveau.fr/Louvorg/ReaderforSelfoss-multiplatform/issues)
|
||||
- [Help translating the app](https://crowdin.com/project/readerforselfoss)
|
||||
|
||||
## Contributors (V1) (Alphabetical order) ❤️
|
||||
|
||||
|
@ -28,7 +28,7 @@ fun gitVersion(): String {
|
||||
val maybeTagOfCurrentCommit = execWithOutput("git -C ../ describe --contains HEAD", true)
|
||||
process = if (maybeTagOfCurrentCommit.isEmpty()) {
|
||||
println("No tag on current commit. Will take the latest one.")
|
||||
execWithOutput("git -C ../ for-each-ref refs/tags --sort=-refname --format='%(refname:short)' --count=1")
|
||||
execWithOutput("git -C ../ for-each-ref refs/tags --sort=-authordate --format='%(refname:short)' --count=1")
|
||||
} else {
|
||||
println("Tag found on current commit")
|
||||
execWithOutput("git -C ../ describe --contains HEAD")
|
||||
@ -56,7 +56,6 @@ fun versionNameFromGit(): String {
|
||||
|
||||
android {
|
||||
compileOptions {
|
||||
isCoreLibraryDesugaringEnabled = true
|
||||
// Flag to enable support for the new language APIs
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
@ -113,8 +112,6 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.3")
|
||||
|
||||
implementation(project(":shared"))
|
||||
implementation("com.google.android.material:material:1.5.0")
|
||||
implementation("androidx.appcompat:appcompat:1.4.1")
|
||||
@ -145,10 +142,11 @@ dependencies {
|
||||
|
||||
// Material-ish things
|
||||
implementation("com.ashokvarma.android:bottom-navigation-bar:2.2.0")
|
||||
implementation("com.amulyakhare:com.amulyakhare.textdrawable:1.0.1")
|
||||
|
||||
// glide
|
||||
kapt("com.github.bumptech.glide:compiler:4.15.0")
|
||||
implementation("com.github.bumptech.glide:okhttp3-integration:4.15.0")
|
||||
kapt("com.github.bumptech.glide:compiler:4.14.2")
|
||||
implementation("com.github.bumptech.glide:okhttp3-integration:4.14.2")
|
||||
|
||||
// Themes
|
||||
implementation("com.github.rubensousa:floatingtoolbar:1.5.1")
|
||||
@ -176,7 +174,7 @@ dependencies {
|
||||
implementation("androidx.lifecycle:lifecycle-extensions:2.2.0")
|
||||
|
||||
// Network information
|
||||
implementation("com.github.ln-12:multiplatform-connectivity-status:1.3.0")
|
||||
implementation("com.github.ln-12:multiplatform-connectivity-status:1.3.0")
|
||||
|
||||
// SQLDELIGHT
|
||||
implementation("com.squareup.sqldelight:android-driver:1.5.4")
|
||||
|
@ -57,7 +57,29 @@ class LoginActivity : AppCompatActivity(), DIAware {
|
||||
|
||||
if (appSettingsService.getBaseUrl().isNotEmpty()) {
|
||||
showProgress(true)
|
||||
goToMain()
|
||||
// This should be reverted when "old" users connected with a non-selfoss rss
|
||||
// are handled. Revert to "simple" way.
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
try {
|
||||
val (errorFetching, displaySelfossOnly) = repository.shouldBeSelfossInstance()
|
||||
if (!errorFetching && !displaySelfossOnly) {
|
||||
goToMain()
|
||||
} else {
|
||||
showProgress(false)
|
||||
if (displaySelfossOnly) {
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
R.string.application_selfoss_only,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
repository.logout()
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
repository.logout()
|
||||
showProgress(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleActions()
|
||||
|
@ -71,12 +71,7 @@ class ReaderActivity : AppCompatActivity(), DIAware {
|
||||
finish()
|
||||
}
|
||||
|
||||
try {
|
||||
readItem(allItems[currentItem])
|
||||
} catch (e: IndexOutOfBoundsException) {
|
||||
e.sendSilentlyWithAcraWithName("out of bound > size = ${allItems.size} currentItem = $currentItem")
|
||||
finish()
|
||||
}
|
||||
readItem(allItems[currentItem])
|
||||
|
||||
binding.pager.adapter = ScreenSlidePagerAdapter(this)
|
||||
binding.pager.setCurrentItem(currentItem, false)
|
||||
|
@ -49,13 +49,13 @@ class SourcesActivity : AppCompatActivity(), DIAware {
|
||||
super.onResume()
|
||||
val mLayoutManager = LinearLayoutManager(this)
|
||||
|
||||
var items: ArrayList<SelfossModel.SourceDetail>
|
||||
var items: ArrayList<SelfossModel.Source>
|
||||
|
||||
binding.recyclerView.setHasFixedSize(true)
|
||||
binding.recyclerView.layoutManager = mLayoutManager
|
||||
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
val response = repository.getSourcesDetails()
|
||||
val response = repository.getSources()
|
||||
if (response.isNotEmpty()) {
|
||||
items = response
|
||||
val mAdapter = SourcesListAdapter(
|
||||
|
@ -24,7 +24,7 @@ import org.kodein.di.instance
|
||||
|
||||
class UpsertSourceActivity : AppCompatActivity(), DIAware {
|
||||
|
||||
private var existingSource: SelfossModel.SourceDetail? = null
|
||||
private var existingSource: SelfossModel.Source? = null
|
||||
private var mSpoutsValue: String? = null
|
||||
|
||||
private lateinit var binding: ActivityUpsertSourceBinding
|
||||
@ -68,7 +68,7 @@ class UpsertSourceActivity : AppCompatActivity(), DIAware {
|
||||
|
||||
private fun initFields(items: Map<String, SelfossModel.Spout>) {
|
||||
binding.nameInput.setText(existingSource!!.title)
|
||||
binding.tags.setText(existingSource!!.tags?.joinToString(", "))
|
||||
binding.tags.setText(existingSource!!.tags.joinToString(", "))
|
||||
binding.sourceUri.setText(existingSource!!.params?.url)
|
||||
binding.spoutsSpinner.setSelection(items.keys.indexOf(existingSource!!.spout))
|
||||
binding.progress.visibility = View.GONE
|
||||
|
@ -9,9 +9,10 @@ import android.widget.ImageView.ScaleType
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import bou.amine.apps.readerforselfossv2.android.R
|
||||
import bou.amine.apps.readerforselfossv2.android.databinding.CardItemBinding
|
||||
import bou.amine.apps.readerforselfossv2.android.model.toTextDrawableString
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.LinkOnTouchListener
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.bitmapCenterCrop
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularDrawable
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularBitmapDrawable
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.openInBrowserAsNewTask
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.openItemUrl
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.shareLink
|
||||
@ -21,6 +22,8 @@ import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
|
||||
import bou.amine.apps.readerforselfossv2.utils.getIcon
|
||||
import bou.amine.apps.readerforselfossv2.utils.getThumbnail
|
||||
import com.amulyakhare.textdrawable.TextDrawable
|
||||
import com.amulyakhare.textdrawable.util.ColorGenerator
|
||||
import com.bumptech.glide.Glide
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@ -35,6 +38,7 @@ class ItemCardAdapter(
|
||||
override val updateItems: (ArrayList<SelfossModel.Item>) -> Unit
|
||||
) : ItemsAdapter<ItemCardAdapter.ViewHolder>() {
|
||||
private val c: Context = app.baseContext
|
||||
private val generator: ColorGenerator = ColorGenerator.MATERIAL
|
||||
private val imageMaxHeight: Int =
|
||||
c.resources.getDimension(R.dimen.card_image_max_height).toInt()
|
||||
|
||||
@ -79,9 +83,16 @@ class ItemCardAdapter(
|
||||
}
|
||||
|
||||
if (itm.getIcon(repository.baseUrl).isEmpty()) {
|
||||
binding.sourceImage.setBackgroundAndText(itm.sourcetitle.getHtmlDecoded())
|
||||
val color = generator.getColor(itm.title.getHtmlDecoded())
|
||||
|
||||
val drawable =
|
||||
TextDrawable
|
||||
.builder()
|
||||
.round()
|
||||
.build(itm.title.getHtmlDecoded().toTextDrawableString(), color)
|
||||
binding.sourceImage.setImageDrawable(drawable)
|
||||
} else {
|
||||
c.circularDrawable(itm.getIcon(repository.baseUrl), binding.sourceImage)
|
||||
c.circularBitmapDrawable(itm.getIcon(repository.baseUrl), binding.sourceImage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,8 +7,10 @@ import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import bou.amine.apps.readerforselfossv2.android.R
|
||||
import bou.amine.apps.readerforselfossv2.android.databinding.ListItemBinding
|
||||
import bou.amine.apps.readerforselfossv2.android.model.toTextDrawableString
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.LinkOnTouchListener
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularDrawable
|
||||
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.openItemUrl
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
@ -16,6 +18,8 @@ import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
|
||||
import bou.amine.apps.readerforselfossv2.utils.getIcon
|
||||
import bou.amine.apps.readerforselfossv2.utils.getThumbnail
|
||||
import com.amulyakhare.textdrawable.TextDrawable
|
||||
import com.amulyakhare.textdrawable.util.ColorGenerator
|
||||
import org.kodein.di.DI
|
||||
import org.kodein.di.android.closestDI
|
||||
import org.kodein.di.instance
|
||||
@ -25,6 +29,7 @@ class ItemListAdapter(
|
||||
override var items: ArrayList<SelfossModel.Item>,
|
||||
override val updateItems: (ArrayList<SelfossModel.Item>) -> Unit
|
||||
) : ItemsAdapter<ItemListAdapter.ViewHolder>() {
|
||||
private val generator: ColorGenerator = ColorGenerator.MATERIAL
|
||||
private val c: Context = app.baseContext
|
||||
|
||||
override val di: DI by closestDI(app)
|
||||
@ -51,12 +56,20 @@ class ItemListAdapter(
|
||||
if (itm.getThumbnail(repository.baseUrl).isEmpty()) {
|
||||
|
||||
if (itm.getIcon(repository.baseUrl).isEmpty()) {
|
||||
binding.itemImage.setBackgroundAndText(itm.sourcetitle.getHtmlDecoded())
|
||||
val color = generator.getColor(itm.title.getHtmlDecoded())
|
||||
|
||||
val drawable =
|
||||
TextDrawable
|
||||
.builder()
|
||||
.round()
|
||||
.build(itm.title.getHtmlDecoded().toTextDrawableString(), color)
|
||||
|
||||
binding.itemImage.setImageDrawable(drawable)
|
||||
} else {
|
||||
c.circularDrawable(itm.getIcon(repository.baseUrl), binding.itemImage)
|
||||
c.circularBitmapDrawable(itm.getIcon(repository.baseUrl), binding.itemImage)
|
||||
}
|
||||
} else {
|
||||
c.circularDrawable(itm.getThumbnail(repository.baseUrl), binding.itemImage)
|
||||
c.bitmapCenterCrop(itm.getThumbnail(repository.baseUrl), binding.itemImage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,14 +10,17 @@ import android.widget.Button
|
||||
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.UpsertSourceActivity
|
||||
import bou.amine.apps.readerforselfossv2.android.R
|
||||
import bou.amine.apps.readerforselfossv2.android.databinding.SourceListItemBinding
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularDrawable
|
||||
import bou.amine.apps.readerforselfossv2.android.model.toTextDrawableString
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularBitmapDrawable
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
|
||||
import bou.amine.apps.readerforselfossv2.utils.getIcon
|
||||
import com.amulyakhare.textdrawable.TextDrawable
|
||||
import com.amulyakhare.textdrawable.util.ColorGenerator
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@ -28,9 +31,10 @@ import org.kodein.di.instance
|
||||
|
||||
class SourcesListAdapter(
|
||||
private val app: Activity,
|
||||
private val items: ArrayList<SelfossModel.SourceDetail>
|
||||
private val items: ArrayList<SelfossModel.Source>
|
||||
) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>(), DIAware {
|
||||
private val c: Context = app.baseContext
|
||||
private val generator: ColorGenerator = ColorGenerator.MATERIAL
|
||||
private lateinit var binding: SourceListItemBinding
|
||||
|
||||
override val di: DI by closestDI(app)
|
||||
@ -45,12 +49,19 @@ class SourcesListAdapter(
|
||||
val itm = items[position]
|
||||
|
||||
if (itm.getIcon(repository.baseUrl).isEmpty()) {
|
||||
binding.itemImage.setBackgroundAndText(itm.title.getHtmlDecoded())
|
||||
val color = generator.getColor(itm.title.getHtmlDecoded())
|
||||
|
||||
val drawable =
|
||||
TextDrawable
|
||||
.builder()
|
||||
.round()
|
||||
.build(itm.title.getHtmlDecoded().toTextDrawableString(), color)
|
||||
binding.itemImage.setImageDrawable(drawable)
|
||||
} else {
|
||||
c.circularDrawable(itm.getIcon(repository.baseUrl), binding.itemImage)
|
||||
c.circularBitmapDrawable(itm.getIcon(repository.baseUrl), binding.itemImage)
|
||||
}
|
||||
|
||||
if (!itm.error.isNullOrBlank()) {
|
||||
if (itm.error.isNotBlank()) {
|
||||
binding.errorText.visibility = View.VISIBLE
|
||||
binding.errorText.text = itm.error
|
||||
} else {
|
||||
|
@ -1,6 +1,5 @@
|
||||
package bou.amine.apps.readerforselfossv2.android.fragments
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.content.res.ColorStateList
|
||||
import android.content.res.TypedArray
|
||||
@ -31,6 +30,7 @@ import bou.amine.apps.readerforselfossv2.android.utils.openInBrowserAsNewTask
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.shareLink
|
||||
import bou.amine.apps.readerforselfossv2.model.MercuryModel
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.model.StatusAndData
|
||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
import bou.amine.apps.readerforselfossv2.rest.MercuryApi
|
||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||
@ -150,19 +150,17 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
|
||||
} catch (e: InflateException) {
|
||||
e.sendSilentlyWithAcraWithName("webview not available")
|
||||
if (context != null) {
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setMessage(requireContext().getString(R.string.webview_dialog_issue_message))
|
||||
.setTitle(requireContext().getString(R.string.webview_dialog_issue_title))
|
||||
.setPositiveButton(
|
||||
android.R.string.ok
|
||||
) { _, _ ->
|
||||
appSettingsService.disableArticleViewer()
|
||||
requireActivity().finish()
|
||||
}
|
||||
.create()
|
||||
.show()
|
||||
}
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setMessage(requireContext().getString(R.string.webview_dialog_issue_message))
|
||||
.setTitle(requireContext().getString(R.string.webview_dialog_issue_title))
|
||||
.setPositiveButton(
|
||||
android.R.string.ok
|
||||
) { _, _ ->
|
||||
appSettingsService.disableArticleViewer()
|
||||
requireActivity().finish()
|
||||
}
|
||||
.create()
|
||||
.show()
|
||||
}
|
||||
|
||||
return binding.root
|
||||
@ -259,8 +257,21 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
try {
|
||||
val response = mercuryApi.query(url)
|
||||
if (response.success && response.data != null) {
|
||||
handleMercuryData(response.data!!)
|
||||
if (response.success && response.data != null && !response.data?.content.isNullOrEmpty()) {
|
||||
binding.titleView.text = response.data!!.title.orEmpty()
|
||||
if (typeface != null) {
|
||||
binding.titleView.typeface = typeface
|
||||
}
|
||||
URL(response.data!!.url)
|
||||
url = response.data!!.url
|
||||
|
||||
contentText = response.data!!.content.orEmpty()
|
||||
htmlToWebview()
|
||||
|
||||
handleLeadImage(response)
|
||||
|
||||
binding.nestedScrollView.scrollTo(0, 0)
|
||||
binding.progressBar.visibility = View.GONE
|
||||
} else {
|
||||
openInBrowserAfterFailing()
|
||||
}
|
||||
@ -273,35 +284,14 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleMercuryData(data: MercuryModel.ParsedContent) {
|
||||
if (data.error == true || data.failed == true) {
|
||||
openInBrowserAfterFailing()
|
||||
} else {
|
||||
binding.titleView.text = data.title.orEmpty()
|
||||
if (typeface != null) {
|
||||
binding.titleView.typeface = typeface
|
||||
}
|
||||
URL(data.url)
|
||||
url = data.url!!
|
||||
|
||||
contentText = data.content.orEmpty()
|
||||
htmlToWebview()
|
||||
|
||||
handleLeadImage(data?.lead_image_url)
|
||||
|
||||
binding.nestedScrollView.scrollTo(0, 0)
|
||||
binding.progressBar.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleLeadImage(lead_image_url: String?) {
|
||||
if (!lead_image_url.isNullOrEmpty() && context != null) {
|
||||
private fun handleLeadImage(response: StatusAndData<MercuryModel.ParsedContent>) {
|
||||
if (!response.data?.lead_image_url.isNullOrEmpty() && context != null) {
|
||||
binding.imageView.visibility = View.VISIBLE
|
||||
Glide
|
||||
.with(requireContext())
|
||||
.asBitmap()
|
||||
.load(
|
||||
lead_image_url
|
||||
response.data!!.lead_image_url.orEmpty()
|
||||
)
|
||||
.apply(RequestOptions.fitCenterTransform())
|
||||
.into(binding.imageView)
|
||||
@ -314,16 +304,10 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
binding.webcontent.webViewClient = object : WebViewClient() {
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun shouldOverrideUrlLoading(view: WebView?, url: String): Boolean {
|
||||
return if (context != null && binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
|
||||
try {
|
||||
requireContext().startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
e.sendSilentlyWithAcraWithName("activityNotFound > $url")
|
||||
}
|
||||
true
|
||||
} else {
|
||||
false
|
||||
if (binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
|
||||
requireContext().startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated in Java")
|
||||
@ -341,7 +325,7 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
getBitmapInputStream(image, Bitmap.CompressFormat.JPEG)
|
||||
)
|
||||
} catch (e: ExecutionException) {
|
||||
// Do nothing
|
||||
e.sendSilentlyWithAcraWithName("shouldInterceptRequest > jpeg > $url")
|
||||
}
|
||||
} else if (url.lowercase(Locale.US).contains(".png")) {
|
||||
try {
|
||||
@ -353,7 +337,7 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
getBitmapInputStream(image, Bitmap.CompressFormat.PNG)
|
||||
)
|
||||
} catch (e: ExecutionException) {
|
||||
// Do nothing
|
||||
e.sendSilentlyWithAcraWithName("shouldInterceptRequest > png > $url")
|
||||
}
|
||||
} else if (url.lowercase(Locale.US).contains(".webp")) {
|
||||
try {
|
||||
@ -365,7 +349,7 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
getBitmapInputStream(image, Bitmap.CompressFormat.WEBP)
|
||||
)
|
||||
} catch (e: ExecutionException) {
|
||||
// Do nothing
|
||||
e.sendSilentlyWithAcraWithName("shouldInterceptRequest > webp > $url")
|
||||
}
|
||||
}
|
||||
|
||||
@ -375,74 +359,74 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
}
|
||||
|
||||
private fun htmlToWebview() {
|
||||
if (context != null) {
|
||||
val attrs: IntArray = intArrayOf(android.R.attr.fontFamily)
|
||||
val a: TypedArray = requireContext().obtainStyledAttributes(resId, attrs)
|
||||
|
||||
val attrs: IntArray = intArrayOf(android.R.attr.fontFamily)
|
||||
val a: TypedArray = requireContext().obtainStyledAttributes(resId, attrs)
|
||||
|
||||
|
||||
binding.webcontent.settings.standardFontFamily = a.getString(0)
|
||||
binding.webcontent.visibility = View.VISIBLE
|
||||
binding.webcontent.settings.standardFontFamily = a.getString(0)
|
||||
binding.webcontent.visibility = View.VISIBLE
|
||||
|
||||
val colorOnSurface = TypedValue()
|
||||
requireContext().theme.resolveAttribute(R.attr.colorOnSurface, colorOnSurface, true)
|
||||
val colorOnSurface = TypedValue()
|
||||
requireContext().theme.resolveAttribute(R.attr.colorOnSurface, colorOnSurface, true)
|
||||
|
||||
val colorSurface = TypedValue()
|
||||
requireContext().theme.resolveAttribute(R.attr.colorSurface, colorSurface, true)
|
||||
val colorSurface = TypedValue()
|
||||
requireContext().theme.resolveAttribute(R.attr.colorSurface, colorSurface, true)
|
||||
|
||||
binding.webcontent.settings.useWideViewPort = true
|
||||
binding.webcontent.settings.loadWithOverviewMode = true
|
||||
binding.webcontent.settings.javaScriptEnabled = false
|
||||
binding.webcontent.settings.useWideViewPort = true
|
||||
binding.webcontent.settings.loadWithOverviewMode = true
|
||||
binding.webcontent.settings.javaScriptEnabled = false
|
||||
|
||||
handleImageLoading()
|
||||
handleImageLoading()
|
||||
|
||||
val gestureDetector =
|
||||
GestureDetector(activity, object : GestureDetector.SimpleOnGestureListener() {
|
||||
override fun onSingleTapUp(e: MotionEvent): Boolean {
|
||||
return performClick()
|
||||
}
|
||||
})
|
||||
val gestureDetector =
|
||||
GestureDetector(activity, object : GestureDetector.SimpleOnGestureListener() {
|
||||
override fun onSingleTapUp(e: MotionEvent): Boolean {
|
||||
return performClick()
|
||||
}
|
||||
})
|
||||
|
||||
binding.webcontent.setOnTouchListener { _, event -> gestureDetector.onTouchEvent(event) }
|
||||
binding.webcontent.setOnTouchListener { _, event -> gestureDetector.onTouchEvent(event) }
|
||||
|
||||
binding.webcontent.settings.layoutAlgorithm =
|
||||
WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
|
||||
binding.webcontent.settings.layoutAlgorithm =
|
||||
WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
|
||||
|
||||
var baseUrl: String? = null
|
||||
var baseUrl: String? = null
|
||||
|
||||
try {
|
||||
val itemUrl = URL(url)
|
||||
baseUrl = itemUrl.protocol + "://" + itemUrl.host
|
||||
} catch (e: MalformedURLException) {
|
||||
e.sendSilentlyWithAcraWithName("htmlToWebview > $url")
|
||||
}
|
||||
try {
|
||||
val itemUrl = URL(url)
|
||||
baseUrl = itemUrl.protocol + "://" + itemUrl.host
|
||||
} catch (e: MalformedURLException) {
|
||||
e.sendSilentlyWithAcraWithName("htmlToWebview > item url")
|
||||
}
|
||||
|
||||
val fontName = when (font) {
|
||||
getString(R.string.open_sans_font_id) -> "Open Sans"
|
||||
getString(R.string.roboto_font_id) -> "Roboto"
|
||||
getString(R.string.source_code_pro_font_id) -> "Source Code Pro"
|
||||
else -> ""
|
||||
}
|
||||
val fontName = when (font) {
|
||||
getString(R.string.open_sans_font_id) -> "Open Sans"
|
||||
getString(R.string.roboto_font_id) -> "Roboto"
|
||||
getString(R.string.source_code_pro_font_id) -> "Source Code Pro"
|
||||
else -> ""
|
||||
}
|
||||
|
||||
val fontLinkAndStyle = if (font.isNotEmpty()) {
|
||||
"""<link href="https://fonts.googleapis.com/css?family=${
|
||||
fontName.replace(
|
||||
" ",
|
||||
"+"
|
||||
)
|
||||
}" rel="stylesheet">
|
||||
val fontLinkAndStyle = if (font.isNotEmpty()) {
|
||||
"""<link href="https://fonts.googleapis.com/css?family=${
|
||||
fontName.replace(
|
||||
" ",
|
||||
"+"
|
||||
)
|
||||
}" rel="stylesheet">
|
||||
|<style>
|
||||
| * {
|
||||
| font-family: '$fontName';
|
||||
| }
|
||||
|</style>
|
||||
""".trimMargin()
|
||||
} else {
|
||||
""
|
||||
}
|
||||
} else {
|
||||
""
|
||||
}
|
||||
|
||||
binding.webcontent.loadDataWithBaseURL(
|
||||
baseUrl,
|
||||
"""<html>
|
||||
binding.webcontent.loadDataWithBaseURL(
|
||||
baseUrl,
|
||||
"""<html>
|
||||
|<head>
|
||||
| <meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
| <style>
|
||||
@ -454,11 +438,11 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
| }
|
||||
| a {
|
||||
| color: ${
|
||||
String.format(
|
||||
"#%06X",
|
||||
0xFFFFFF and resources.getColor(R.color.colorAccent)
|
||||
)
|
||||
} !important;
|
||||
String.format(
|
||||
"#%06X",
|
||||
0xFFFFFF and resources.getColor(R.color.colorAccent)
|
||||
)
|
||||
} !important;
|
||||
| }
|
||||
| *:not(a) {
|
||||
| color: ${String.format("#%06X", 0xFFFFFF and colorOnSurface.data)};
|
||||
@ -470,25 +454,25 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
| overflow:hidden;
|
||||
| line-height: 1.5em;
|
||||
| background-color: ${
|
||||
String.format(
|
||||
"#%06X",
|
||||
0xFFFFFF and colorSurface.data
|
||||
)
|
||||
};
|
||||
String.format(
|
||||
"#%06X",
|
||||
0xFFFFFF and colorSurface.data
|
||||
)
|
||||
};
|
||||
| }
|
||||
| body, html {
|
||||
| background-color: ${
|
||||
String.format(
|
||||
"#%06X",
|
||||
0xFFFFFF and colorSurface.data
|
||||
)
|
||||
} !important;
|
||||
String.format(
|
||||
"#%06X",
|
||||
0xFFFFFF and colorSurface.data
|
||||
)
|
||||
} !important;
|
||||
| border-color: ${
|
||||
String.format(
|
||||
"#%06X",
|
||||
0xFFFFFF and colorSurface.data
|
||||
)
|
||||
} !important;
|
||||
String.format(
|
||||
"#%06X",
|
||||
0xFFFFFF and colorSurface.data
|
||||
)
|
||||
} !important;
|
||||
| padding: 0 !important;
|
||||
| margin: 0 !important;
|
||||
| }
|
||||
@ -499,11 +483,11 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
| white-space: pre-wrap;
|
||||
| width:100%;
|
||||
| background-color: ${
|
||||
String.format(
|
||||
"#%06X",
|
||||
0xFFFFFF and colorSurface.data
|
||||
)
|
||||
};
|
||||
String.format(
|
||||
"#%06X",
|
||||
0xFFFFFF and colorSurface.data
|
||||
)
|
||||
};
|
||||
| }
|
||||
| </style>
|
||||
| $fontLinkAndStyle
|
||||
@ -511,11 +495,10 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
|<body>
|
||||
| $contentText
|
||||
|</body>""".trimMargin(),
|
||||
"text/html",
|
||||
"utf-8",
|
||||
null
|
||||
)
|
||||
}
|
||||
"text/html",
|
||||
"utf-8",
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
fun scrollDown() {
|
||||
|
@ -20,8 +20,10 @@ import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
|
||||
import bou.amine.apps.readerforselfossv2.utils.getIcon
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.target.ViewTarget
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
import com.bumptech.glide.load.DataSource
|
||||
import com.bumptech.glide.load.engine.GlideException
|
||||
import com.bumptech.glide.request.RequestListener
|
||||
import com.bumptech.glide.request.target.Target
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import com.google.android.material.chip.Chip
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@ -82,25 +84,37 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
|
||||
) {
|
||||
val sourceGroup = binding.sourcesGroup
|
||||
|
||||
repository.getSourcesDetailsOrStats().forEach { source ->
|
||||
repository.getSources().forEach { source ->
|
||||
val c = Chip(context)
|
||||
c.ellipsize = TextUtils.TruncateAt.END
|
||||
|
||||
Glide.with(context)
|
||||
.load(source.getIcon(repository.baseUrl))
|
||||
.into(object : ViewTarget<Chip?, Drawable?>(c) {
|
||||
.listener(object : RequestListener<Drawable?> {
|
||||
override fun onLoadFailed(
|
||||
e: GlideException?,
|
||||
model: Any?,
|
||||
target: Target<Drawable?>?,
|
||||
isFirstResource: Boolean
|
||||
): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onResourceReady(
|
||||
resource: Drawable,
|
||||
transition: Transition<in Drawable?>?
|
||||
) {
|
||||
resource: Drawable?,
|
||||
model: Any?,
|
||||
target: Target<Drawable?>?,
|
||||
dataSource: DataSource?,
|
||||
isFirstResource: Boolean
|
||||
): Boolean {
|
||||
try {
|
||||
c.chipIcon = resource
|
||||
} catch (e: Exception) {
|
||||
e.sendSilentlyWithAcraWithName("sources > onResourceReady")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
})
|
||||
}).preload()
|
||||
|
||||
c.text = source.title.getHtmlDecoded()
|
||||
|
||||
@ -127,9 +141,9 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
|
||||
selectedChip = c
|
||||
}
|
||||
|
||||
c.isEnabled = source.error.isNullOrBlank()
|
||||
c.isEnabled = source.error.isBlank()
|
||||
|
||||
if (!source.error.isNullOrBlank() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
if (source.error.isNotBlank() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
c.tooltipText = source.error
|
||||
}
|
||||
|
||||
|
@ -1,62 +0,0 @@
|
||||
package bou.amine.apps.readerforselfossv2.android.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.RelativeLayout
|
||||
import android.widget.TextView
|
||||
import bou.amine.apps.readerforselfossv2.android.R
|
||||
import bou.amine.apps.readerforselfossv2.android.model.toTextDrawableString
|
||||
import com.google.android.material.imageview.ShapeableImageView
|
||||
import kotlin.math.abs
|
||||
|
||||
class CircleImageView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : RelativeLayout(context, attrs, defStyleAttr) {
|
||||
val view: View
|
||||
val imageView: ShapeableImageView
|
||||
val textView: TextView
|
||||
|
||||
private val colorScheme = listOf(
|
||||
-0x1a8c8d,
|
||||
-0xf9d6e,
|
||||
-0x459738,
|
||||
-0x6a8a33,
|
||||
-0x867935,
|
||||
-0x9b4a0a,
|
||||
-0xb03c09,
|
||||
-0xb22f1f,
|
||||
-0xb24954,
|
||||
-0x7e387c,
|
||||
-0x512a7f,
|
||||
-0x759b,
|
||||
-0x2b1ea9,
|
||||
-0x2ab1,
|
||||
-0x48b3,
|
||||
-0x5e7781,
|
||||
-0x6f5b52
|
||||
)
|
||||
|
||||
init {
|
||||
view = LayoutInflater.from(context).inflate(R.layout.circle_image_view, this, true)
|
||||
imageView = view.findViewById(R.id.circleImage)
|
||||
textView = view.findViewById(R.id.circleText)
|
||||
}
|
||||
|
||||
fun setBackgroundAndText(text: String) {
|
||||
val circleDrawable = GradientDrawable()
|
||||
val color = colorFromIdentifier(text)
|
||||
circleDrawable.setColor(color)
|
||||
imageView.setImageDrawable(circleDrawable)
|
||||
|
||||
textView.text = text.toTextDrawableString()
|
||||
}
|
||||
|
||||
private fun colorFromIdentifier(key: String): Int {
|
||||
return colorScheme[abs(key.hashCode()) % colorScheme.size]
|
||||
}
|
||||
}
|
@ -3,9 +3,10 @@ package bou.amine.apps.readerforselfossv2.android.utils.glide
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.widget.ImageView
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.CircleImageView
|
||||
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import com.bumptech.glide.request.target.BitmapImageViewTarget
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.InputStream
|
||||
@ -17,13 +18,21 @@ fun Context.bitmapCenterCrop(url: String, iv: ImageView) =
|
||||
.apply(RequestOptions.centerCropTransform())
|
||||
.into(iv)
|
||||
|
||||
fun Context.circularDrawable(url: String, view: CircleImageView) {
|
||||
view.textView.text =""
|
||||
|
||||
fun Context.circularBitmapDrawable(url: String, iv: ImageView) =
|
||||
Glide.with(this)
|
||||
.asBitmap()
|
||||
.load(url)
|
||||
.into(view.imageView)
|
||||
}
|
||||
.apply(RequestOptions.centerCropTransform())
|
||||
.into(object : BitmapImageViewTarget(iv) {
|
||||
override fun setResource(resource: Bitmap?) {
|
||||
val circularBitmapDrawable = RoundedBitmapDrawableFactory.create(
|
||||
resources,
|
||||
resource
|
||||
)
|
||||
circularBitmapDrawable.isCircular = true
|
||||
iv.setImageDrawable(circularBitmapDrawable)
|
||||
}
|
||||
})
|
||||
|
||||
fun getBitmapInputStream(bitmap:Bitmap,compressFormat: Bitmap.CompressFormat): InputStream {
|
||||
val byteArrayOutputStream = ByteArrayOutputStream()
|
||||
|
@ -39,7 +39,7 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/itemImage">
|
||||
|
||||
<bou.amine.apps.readerforselfossv2.android.utils.CircleImageView
|
||||
<ImageView
|
||||
android:id="@+id/sourceImage"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
|
@ -1,26 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/circleImage"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerCrop"
|
||||
app:shapeAppearanceOverlay="@style/circleImageView"
|
||||
app:srcCompat="@drawable/background_splash" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/circleText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:ellipsize="none"
|
||||
android:gravity="center"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/white"
|
||||
android:textIsSelectable="false"
|
||||
android:textSize="20sp"
|
||||
android:typeface="normal" />
|
||||
</RelativeLayout>
|
@ -5,7 +5,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<bou.amine.apps.readerforselfossv2.android.utils.CircleImageView
|
||||
<ImageView
|
||||
android:id="@+id/itemImage"
|
||||
android:layout_width="46dp"
|
||||
android:layout_height="46dp"
|
||||
|
@ -25,7 +25,7 @@
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.0" />
|
||||
|
||||
<bou.amine.apps.readerforselfossv2.android.utils.CircleImageView
|
||||
<ImageView
|
||||
android:id="@+id/itemImage"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
|
@ -32,10 +32,4 @@
|
||||
<item name="android:colorBackgroundCacheHint">@null</item>
|
||||
<item name="android:windowIsTranslucent">true</item>
|
||||
</style>
|
||||
|
||||
<style name="circleImageView" parent="">
|
||||
<item name="cornerFamily">rounded</item>
|
||||
<item name="cornerSize">50%</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
@ -300,10 +300,9 @@ class RepositoryTest {
|
||||
every { appSettingsService.isItemCachingEnabled() } returns true
|
||||
|
||||
initializeRepository(MutableStateFlow(false))
|
||||
repository.setSourceFilter(SelfossModel.SourceDetail(
|
||||
repository.setSourceFilter(SelfossModel.Source(
|
||||
1,
|
||||
"Test",
|
||||
null,
|
||||
listOf("tags"),
|
||||
SPOUT,
|
||||
"",
|
||||
@ -610,32 +609,30 @@ class RepositoryTest {
|
||||
fun get_sources() {
|
||||
val (sources, sourcesDB) = prepareSources()
|
||||
initializeRepository()
|
||||
var testSources: List<SelfossModel.Source>
|
||||
var testSources: List<SelfossModel.Source>?
|
||||
runBlocking {
|
||||
testSources = repository.getSourcesDetails()
|
||||
testSources = repository.getSources()
|
||||
}
|
||||
|
||||
assertEquals(sources, testSources)
|
||||
assertSame(sources, testSources)
|
||||
assertNotEquals(sourcesDB.map { it.toView() }, testSources)
|
||||
coVerify(exactly = 1) { api.sourcesDetailed() }
|
||||
coVerify(exactly = 1) { api.sources() }
|
||||
}
|
||||
|
||||
private fun prepareSources(): Pair<ArrayList<SelfossModel.SourceDetail>, List<SOURCE>> {
|
||||
private fun prepareSources(): Pair<ArrayList<SelfossModel.Source>, List<SOURCE>> {
|
||||
val sources = arrayListOf(
|
||||
SelfossModel.SourceDetail(
|
||||
SelfossModel.Source(
|
||||
1,
|
||||
"First source",
|
||||
null,
|
||||
listOf("Test", "second"),
|
||||
SPOUT,
|
||||
"",
|
||||
IMAGE_URL_2,
|
||||
SelfossModel.SourceParams("url")
|
||||
),
|
||||
SelfossModel.SourceDetail(
|
||||
SelfossModel.Source(
|
||||
2,
|
||||
"Second source",
|
||||
null,
|
||||
listOf("second"),
|
||||
SPOUT,
|
||||
"",
|
||||
@ -664,7 +661,7 @@ class RepositoryTest {
|
||||
)
|
||||
)
|
||||
|
||||
coEvery { api.sourcesDetailed() } returns StatusAndData(success = true, data = sources)
|
||||
coEvery { api.sources() } returns StatusAndData(success = true, data = sources)
|
||||
every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB
|
||||
return Pair(sources, sourcesDB)
|
||||
}
|
||||
@ -678,13 +675,13 @@ class RepositoryTest {
|
||||
initializeRepository()
|
||||
var testSources: List<SelfossModel.Source>?
|
||||
runBlocking {
|
||||
testSources = repository.getSourcesDetails()
|
||||
testSources = repository.getSources()
|
||||
// Sources will be fetched from the database on the second call, thus testSources != sources
|
||||
testSources = repository.getSourcesDetails()
|
||||
testSources = repository.getSources()
|
||||
}
|
||||
|
||||
coVerify(exactly = 1) { api.sourcesDetailed() }
|
||||
assertNotEquals(sources, testSources)
|
||||
coVerify(exactly = 1) { api.sources() }
|
||||
assertNotSame(sources, testSources)
|
||||
assertEquals(sourcesDB.map { it.toView() }, testSources)
|
||||
verify(atLeast = 1) { db.sourcesQueries.sources().executeAsList() }
|
||||
}
|
||||
@ -696,13 +693,13 @@ class RepositoryTest {
|
||||
every { appSettingsService.isUpdateSourcesEnabled() } returns true
|
||||
every { appSettingsService.isItemCachingEnabled() } returns false
|
||||
initializeRepository()
|
||||
var testSources: List<SelfossModel.Source>
|
||||
var testSources: List<SelfossModel.Source>?
|
||||
runBlocking {
|
||||
testSources = repository.getSourcesDetails()
|
||||
testSources = repository.getSources()
|
||||
}
|
||||
|
||||
assertEquals(sources, testSources)
|
||||
coVerify(exactly = 1) { api.sourcesDetailed() }
|
||||
assertSame(sources, testSources)
|
||||
coVerify(exactly = 1) { api.sources() }
|
||||
verify(exactly = 0) { db.sourcesQueries }
|
||||
}
|
||||
|
||||
@ -713,13 +710,13 @@ class RepositoryTest {
|
||||
every { appSettingsService.isUpdateSourcesEnabled() } returns false
|
||||
every { appSettingsService.isItemCachingEnabled() } returns false
|
||||
initializeRepository()
|
||||
var testSources: List<SelfossModel.Source>
|
||||
var testSources: List<SelfossModel.Source>?
|
||||
runBlocking {
|
||||
testSources = repository.getSourcesDetails()
|
||||
testSources = repository.getSources()
|
||||
}
|
||||
|
||||
assertEquals(sources, testSources)
|
||||
coVerify(exactly = 1) { api.sourcesDetailed() }
|
||||
assertSame(sources, testSources)
|
||||
coVerify(exactly = 1) { api.sources() }
|
||||
verify(atLeast = 1) { db.sourcesQueries }
|
||||
}
|
||||
|
||||
@ -727,13 +724,13 @@ class RepositoryTest {
|
||||
fun get_sources_without_connection() {
|
||||
val (_, sourcesDB) = prepareSources()
|
||||
initializeRepository(MutableStateFlow(false))
|
||||
var testSources: List<SelfossModel.Source>
|
||||
var testSources: List<SelfossModel.Source>?
|
||||
runBlocking {
|
||||
testSources = repository.getSourcesDetails()
|
||||
testSources = repository.getSources()
|
||||
}
|
||||
|
||||
assertEquals(sourcesDB.map { it.toView() }, testSources)
|
||||
coVerify(exactly = 0) { api.sourcesDetailed() }
|
||||
coVerify(exactly = 0) { api.sources() }
|
||||
verify(atLeast = 1) { db.sourcesQueries.sources().executeAsList() }
|
||||
}
|
||||
|
||||
@ -744,13 +741,13 @@ class RepositoryTest {
|
||||
every { appSettingsService.isItemCachingEnabled() } returns false
|
||||
every { appSettingsService.isUpdateSourcesEnabled() } returns true
|
||||
initializeRepository(MutableStateFlow(false))
|
||||
var testSources: List<SelfossModel.Source>
|
||||
var testSources: List<SelfossModel.Source>?
|
||||
runBlocking {
|
||||
testSources = repository.getSourcesDetails()
|
||||
testSources = repository.getSources()
|
||||
}
|
||||
|
||||
assertEquals(emptyList<SelfossModel.Source>(), testSources)
|
||||
coVerify(exactly = 0) { api.sourcesDetailed() }
|
||||
coVerify(exactly = 0) { api.sources() }
|
||||
verify(exactly = 0) { db.sourcesQueries.sources().executeAsList() }
|
||||
}
|
||||
|
||||
@ -761,13 +758,13 @@ class RepositoryTest {
|
||||
every { appSettingsService.isItemCachingEnabled() } returns true
|
||||
every { appSettingsService.isUpdateSourcesEnabled() } returns false
|
||||
initializeRepository(MutableStateFlow(false))
|
||||
var testSources: List<SelfossModel.Source>
|
||||
var testSources: List<SelfossModel.Source>?
|
||||
runBlocking {
|
||||
testSources = repository.getSourcesDetails()
|
||||
testSources = repository.getSources()
|
||||
}
|
||||
|
||||
assertEquals(sourcesDB.map { it.toView() }, testSources)
|
||||
coVerify(exactly = 0) { api.sourcesDetailed() }
|
||||
coVerify(exactly = 0) { api.sources() }
|
||||
verify(atLeast = 1) { db.sourcesQueries.sources().executeAsList() }
|
||||
}
|
||||
|
||||
@ -778,13 +775,13 @@ class RepositoryTest {
|
||||
every { appSettingsService.isItemCachingEnabled() } returns false
|
||||
every { appSettingsService.isUpdateSourcesEnabled() } returns false
|
||||
initializeRepository(MutableStateFlow(false))
|
||||
var testSources: List<SelfossModel.Source>
|
||||
var testSources: List<SelfossModel.Source>?
|
||||
runBlocking {
|
||||
testSources = repository.getSourcesDetails()
|
||||
testSources = repository.getSources()
|
||||
}
|
||||
|
||||
assertEquals(sourcesDB.map { it.toView() }, testSources)
|
||||
coVerify(exactly = 0) { api.sourcesDetailed() }
|
||||
coVerify(exactly = 0) { api.sources() }
|
||||
verify(atLeast = 1) { db.sourcesQueries.sources().executeAsList() }
|
||||
}
|
||||
|
||||
@ -1105,10 +1102,9 @@ class RepositoryTest {
|
||||
private fun prepareSearch() {
|
||||
repository.setTagFilter(SelfossModel.Tag("Tag", "read", 0))
|
||||
repository.setSourceFilter(
|
||||
SelfossModel.SourceDetail(
|
||||
SelfossModel.Source(
|
||||
1,
|
||||
"First source",
|
||||
5,
|
||||
listOf("Test", "second"),
|
||||
SPOUT,
|
||||
"",
|
||||
|
@ -9,9 +9,6 @@ class MercuryModel {
|
||||
val title: String?,
|
||||
val content: String?,
|
||||
val lead_image_url: String?, // NOSONAR
|
||||
val url: String?,
|
||||
val error: Boolean?,
|
||||
val message: String?,
|
||||
val failed: Boolean?
|
||||
val url: String
|
||||
)
|
||||
}
|
||||
|
@ -24,8 +24,8 @@ class SelfossModel {
|
||||
@Serializable
|
||||
class Stats(
|
||||
val total: Int,
|
||||
val unread: Int?,
|
||||
val starred: Int?
|
||||
val unread: Int,
|
||||
val starred: Int
|
||||
)
|
||||
|
||||
@Serializable
|
||||
@ -63,39 +63,20 @@ class SelfossModel {
|
||||
fun isPublicModeEnabled() = publicMode ?: false
|
||||
}
|
||||
|
||||
interface Source {
|
||||
val id: Int
|
||||
var title: String
|
||||
var unread: Int?
|
||||
var error: String?
|
||||
var icon: String?
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class SourceStats(
|
||||
override val id: Int,
|
||||
override var title: String,
|
||||
override var unread: Int?,
|
||||
override var error: String? = null,
|
||||
override var icon: String? = null
|
||||
) : Source
|
||||
|
||||
@Serializable
|
||||
data class SourceDetail(
|
||||
override val id: Int,
|
||||
override var title: String,
|
||||
override var unread: Int? = null,
|
||||
data class Source(
|
||||
val id: Int,
|
||||
val title: String,
|
||||
@Serializable(with = TagsListSerializer::class)
|
||||
var tags: List<String>?,
|
||||
var spout: String?,
|
||||
override var error: String?,
|
||||
override var icon: String?,
|
||||
var params: SourceParams?
|
||||
) : Source
|
||||
|
||||
val tags: List<String>,
|
||||
val spout: String,
|
||||
val error: String,
|
||||
val icon: String?,
|
||||
val params: SourceParams?
|
||||
)
|
||||
@Serializable
|
||||
data class SourceParams(
|
||||
val url: String? = null
|
||||
val url: String
|
||||
)
|
||||
@Serializable
|
||||
data class Item(
|
||||
|
@ -44,11 +44,11 @@ class Repository(
|
||||
private val _badgeStarred = MutableStateFlow(0)
|
||||
val badgeStarred = _badgeStarred.asStateFlow()
|
||||
|
||||
private var fetchedTags = false
|
||||
private var fetchedSources = false
|
||||
private var fetchedTags = false
|
||||
|
||||
private var _readerItems = ArrayList<SelfossModel.Item>()
|
||||
private var _selectedSource: SelfossModel.SourceDetail? = null
|
||||
private var _selectedSource: SelfossModel.Source? = null
|
||||
|
||||
suspend fun getNewerItems(): ArrayList<SelfossModel.Item> {
|
||||
var fetchedItems: StatusAndData<List<SelfossModel.Item>> = StatusAndData.error()
|
||||
@ -132,9 +132,9 @@ class Repository(
|
||||
if (isNetworkAvailable()) {
|
||||
val response = api.stats()
|
||||
if (response.success && response.data != null) {
|
||||
_badgeUnread.value = response.data.unread ?: 0
|
||||
_badgeUnread.value = response.data.unread
|
||||
_badgeAll.value = response.data.total
|
||||
_badgeStarred.value = response.data.starred ?: 0
|
||||
_badgeStarred.value = response.data.starred
|
||||
success = true
|
||||
}
|
||||
} else if (appSettingsService.isItemCachingEnabled()) {
|
||||
@ -180,46 +180,23 @@ class Repository(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getSourcesDetailsOrStats(): ArrayList<SelfossModel.Source> {
|
||||
var sources = ArrayList<SelfossModel.Source>()
|
||||
suspend fun getSources(): ArrayList<SelfossModel.Source> {
|
||||
val isDatabaseEnabled =
|
||||
appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled()
|
||||
val shouldFetch = if (!appSettingsService.isUpdateSourcesEnabled()) !fetchedSources else true
|
||||
if (shouldFetch && isNetworkAvailable()) {
|
||||
if (appSettingsService.getPublicAccess()) {
|
||||
val apiSources = api.sourcesStats()
|
||||
if (apiSources.success && apiSources.data != null) {
|
||||
return if (isNetworkAvailable() && !fetchedSources) {
|
||||
val apiSources = api.sources()
|
||||
if (apiSources.success && apiSources.data != null && isDatabaseEnabled) {
|
||||
resetDBSourcesWithData(apiSources.data)
|
||||
if (!appSettingsService.isUpdateSourcesEnabled()) {
|
||||
fetchedSources = true
|
||||
sources = apiSources.data as ArrayList<SelfossModel.Source>
|
||||
}
|
||||
} else {
|
||||
sources = getSourcesDetails() as ArrayList<SelfossModel.Source>
|
||||
}
|
||||
} else if (isDatabaseEnabled) {
|
||||
sources = getDBSources().map { it.toView() } as ArrayList<SelfossModel.Source>
|
||||
}
|
||||
|
||||
return sources
|
||||
}
|
||||
|
||||
suspend fun getSourcesDetails(): ArrayList<SelfossModel.SourceDetail> {
|
||||
var sources = ArrayList<SelfossModel.SourceDetail>()
|
||||
val isDatabaseEnabled =
|
||||
appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled()
|
||||
val shouldFetch = if (!appSettingsService.isUpdateSourcesEnabled()) !fetchedSources else true
|
||||
if (shouldFetch && isNetworkAvailable()) {
|
||||
val apiSources = api.sourcesDetailed()
|
||||
if (apiSources.success && apiSources.data != null) {
|
||||
fetchedSources = true
|
||||
sources = apiSources.data
|
||||
if (isDatabaseEnabled) {
|
||||
resetDBSourcesWithData(sources)
|
||||
}
|
||||
}
|
||||
apiSources.data ?: ArrayList()
|
||||
} else if (isDatabaseEnabled) {
|
||||
sources = getDBSources().map { it.toView() } as ArrayList<SelfossModel.SourceDetail>
|
||||
ArrayList(getDBSources().map { it.toView() })
|
||||
} else {
|
||||
ArrayList()
|
||||
}
|
||||
return sources
|
||||
}
|
||||
|
||||
suspend fun markAsRead(item: SelfossModel.Item): Boolean {
|
||||
@ -505,7 +482,7 @@ class Repository(
|
||||
}
|
||||
}
|
||||
|
||||
private fun resetDBSourcesWithData(sources: List<SelfossModel.SourceDetail>) {
|
||||
private fun resetDBSourcesWithData(sources: List<SelfossModel.Source>) {
|
||||
db.sourcesQueries.deleteAllSources()
|
||||
|
||||
db.sourcesQueries.transaction {
|
||||
@ -615,7 +592,7 @@ class Repository(
|
||||
ReaderForSelfossDB.Schema.migrate(driverFactory.createDriver(), 0, 1)
|
||||
}
|
||||
|
||||
fun setSelectedSource(source: SelfossModel.SourceDetail) {
|
||||
fun setSelectedSource(source: SelfossModel.Source) {
|
||||
_selectedSource = source
|
||||
}
|
||||
|
||||
@ -623,7 +600,7 @@ class Repository(
|
||||
_selectedSource = null
|
||||
}
|
||||
|
||||
fun getSelectedSource(): SelfossModel.SourceDetail? {
|
||||
fun getSelectedSource(): SelfossModel.Source? {
|
||||
return _selectedSource
|
||||
}
|
||||
}
|
@ -7,7 +7,6 @@ import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||
import io.github.aakira.napier.Napier
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.plugins.*
|
||||
import io.ktor.client.plugins.auth.providers.*
|
||||
import io.ktor.client.plugins.cache.*
|
||||
import io.ktor.client.plugins.contentnegotiation.*
|
||||
import io.ktor.client.plugins.cookies.*
|
||||
@ -16,9 +15,6 @@ import io.ktor.client.request.*
|
||||
import io.ktor.client.statement.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.serialization.kotlinx.json.*
|
||||
import io.ktor.util.*
|
||||
import io.ktor.utils.io.charsets.*
|
||||
import io.ktor.utils.io.core.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@ -67,7 +63,6 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
expectSuccess = false
|
||||
}
|
||||
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
@ -79,13 +74,6 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
client = createHttpClient()
|
||||
}
|
||||
|
||||
fun constructBasicAuthValue(credentials: BasicAuthCredentials): String {
|
||||
val authString = "${credentials.username}:${credentials.password}"
|
||||
val authBuf = authString.toByteArray(Charsets.UTF_8).encodeBase64()
|
||||
|
||||
return "Basic $authBuf"
|
||||
}
|
||||
|
||||
// Api version was introduces after the POST login, so when there is a version, it should be available
|
||||
private fun shouldHavePostLogin() = appSettingsService.getApiVersion() != -1
|
||||
private fun hasLoginInfo() =
|
||||
@ -108,23 +96,11 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
private suspend fun getLogin() = maybeResponse(client.tryToGet(url("/login")) {
|
||||
parameter("username", appSettingsService.getUserName())
|
||||
parameter("password", appSettingsService.getPassword())
|
||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
||||
headers {
|
||||
append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
private suspend fun postLogin() = maybeResponse(client.tryToPost(url("/login")) {
|
||||
parameter("username", appSettingsService.getUserName())
|
||||
parameter("password", appSettingsService.getPassword())
|
||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
||||
headers {
|
||||
append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
private fun shouldHaveNewLogout() =
|
||||
@ -138,23 +114,9 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
}
|
||||
|
||||
private suspend fun maybeLogoutIfAvailable() =
|
||||
responseOrSuccessIf404(client.tryToGet(url("/logout")) {
|
||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
||||
headers {
|
||||
append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
responseOrSuccessIf404(client.tryToGet(url("/logout")))
|
||||
|
||||
private suspend fun doLogout() = maybeResponse(client.tryToDelete(url("/api/session/current")) {
|
||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
||||
headers {
|
||||
append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
private suspend fun doLogout() = maybeResponse(client.tryToDelete(url("/api/session/current")))
|
||||
|
||||
suspend fun getItems(
|
||||
type: String,
|
||||
@ -177,12 +139,6 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
parameter("updatedsince", updatedSince)
|
||||
parameter("items", items ?: appSettingsService.getItemsNumber())
|
||||
parameter("offset", offset)
|
||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
||||
headers {
|
||||
append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
suspend fun getItemsWithoutCatch(): StatusAndData<List<SelfossModel.Item>> =
|
||||
@ -193,12 +149,6 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
}
|
||||
parameter("type", "all")
|
||||
parameter("items", 1)
|
||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
||||
headers {
|
||||
append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
suspend fun stats(): StatusAndData<SelfossModel.Stats> =
|
||||
@ -207,12 +157,6 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
parameter("username", appSettingsService.getUserName())
|
||||
parameter("password", appSettingsService.getPassword())
|
||||
}
|
||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
||||
headers {
|
||||
append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
suspend fun tags(): StatusAndData<List<SelfossModel.Tag>> =
|
||||
@ -221,12 +165,6 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
parameter("username", appSettingsService.getUserName())
|
||||
parameter("password", appSettingsService.getPassword())
|
||||
}
|
||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
||||
headers {
|
||||
append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
suspend fun update(): StatusAndData<String> =
|
||||
@ -235,12 +173,6 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
parameter("username", appSettingsService.getUserName())
|
||||
parameter("password", appSettingsService.getPassword())
|
||||
}
|
||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
||||
headers {
|
||||
append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
suspend fun spouts(): StatusAndData<Map<String, SelfossModel.Spout>> =
|
||||
@ -249,51 +181,18 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
parameter("username", appSettingsService.getUserName())
|
||||
parameter("password", appSettingsService.getPassword())
|
||||
}
|
||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
||||
headers {
|
||||
append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
suspend fun sourcesStats(): StatusAndData<ArrayList<SelfossModel.SourceStats>> =
|
||||
bodyOrFailure(client.tryToGet(url("/sources/stats")) {
|
||||
if (!shouldHavePostLogin()) {
|
||||
parameter("username", appSettingsService.getUserName())
|
||||
parameter("password", appSettingsService.getPassword())
|
||||
}
|
||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
||||
headers {
|
||||
append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
suspend fun sourcesDetailed(): StatusAndData<ArrayList<SelfossModel.SourceDetail>> =
|
||||
suspend fun sources(): StatusAndData<ArrayList<SelfossModel.Source>> =
|
||||
bodyOrFailure(client.tryToGet(url("/sources/list")) {
|
||||
if (!shouldHavePostLogin()) {
|
||||
parameter("username", appSettingsService.getUserName())
|
||||
parameter("password", appSettingsService.getPassword())
|
||||
}
|
||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
||||
headers {
|
||||
append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
suspend fun apiInformation(): StatusAndData<SelfossModel.ApiInformation> =
|
||||
bodyOrFailure(client.tryToGet(url("/api/about")) {
|
||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
||||
headers {
|
||||
append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
bodyOrFailure(client.tryToGet(url("/api/about")))
|
||||
|
||||
suspend fun markAsRead(id: String): SuccessResponse =
|
||||
maybeResponse(client.tryToPost(url("/mark/$id")) {
|
||||
@ -301,12 +200,6 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
parameter("username", appSettingsService.getUserName())
|
||||
parameter("password", appSettingsService.getPassword())
|
||||
}
|
||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
||||
headers {
|
||||
append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
suspend fun unmarkAsRead(id: String): SuccessResponse =
|
||||
@ -315,12 +208,6 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
parameter("username", appSettingsService.getUserName())
|
||||
parameter("password", appSettingsService.getPassword())
|
||||
}
|
||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
||||
headers {
|
||||
append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
suspend fun starr(id: String): SuccessResponse =
|
||||
@ -329,12 +216,6 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
parameter("username", appSettingsService.getUserName())
|
||||
parameter("password", appSettingsService.getPassword())
|
||||
}
|
||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
||||
headers {
|
||||
append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
suspend fun unstarr(id: String): SuccessResponse =
|
||||
@ -343,12 +224,6 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
parameter("username", appSettingsService.getUserName())
|
||||
parameter("password", appSettingsService.getPassword())
|
||||
}
|
||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
||||
headers {
|
||||
append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
suspend fun markAllAsRead(ids: List<String>): SuccessResponse =
|
||||
@ -360,14 +235,6 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
append("password", appSettingsService.getPassword())
|
||||
}
|
||||
ids.map { append("ids[]", it) }
|
||||
},
|
||||
block = {
|
||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
||||
headers {
|
||||
append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
))
|
||||
|
||||
@ -403,14 +270,6 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
append("url", url)
|
||||
append("spout", spout)
|
||||
append(tagsParamName, tags)
|
||||
},
|
||||
block = {
|
||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
||||
headers {
|
||||
append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@ -448,14 +307,6 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
append("url", url)
|
||||
append("spout", spout)
|
||||
append(tagsParamName, tags)
|
||||
},
|
||||
block = {
|
||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
||||
headers {
|
||||
append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@ -465,18 +316,5 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
parameter("username", appSettingsService.getUserName())
|
||||
parameter("password", appSettingsService.getPassword())
|
||||
}
|
||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
||||
headers {
|
||||
append(
|
||||
HttpHeaders.Authorization,
|
||||
constructBasicAuthValue(
|
||||
BasicAuthCredentials(
|
||||
username = appSettingsService.getBasicUserName(),
|
||||
password = appSettingsService.getBasicPassword()
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
@ -10,9 +10,7 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
|
||||
private var _publicAccess: Boolean? = null
|
||||
private var _baseUrl: String = ""
|
||||
private var _userName: String = ""
|
||||
private var _basicUserName: String = ""
|
||||
private var _password: String = ""
|
||||
private var _basicPassword: String = ""
|
||||
|
||||
// User settings related
|
||||
private var _itemsCaching: Boolean? = null
|
||||
@ -98,20 +96,6 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
|
||||
return _password
|
||||
}
|
||||
|
||||
fun getBasicUserName(): String {
|
||||
if (_basicUserName.isEmpty()) {
|
||||
refreshBasicUsername()
|
||||
}
|
||||
return _basicUserName
|
||||
}
|
||||
|
||||
fun getBasicPassword(): String {
|
||||
if (_basicPassword.isEmpty()) {
|
||||
refreshBasicPassword()
|
||||
}
|
||||
return _basicPassword
|
||||
}
|
||||
|
||||
fun getItemsNumber(): Int {
|
||||
if (_itemsNumber == null) {
|
||||
refreshItemsNumber()
|
||||
@ -165,14 +149,6 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
|
||||
_password = settings.getString(PASSWORD, "")
|
||||
}
|
||||
|
||||
private fun refreshBasicUsername() {
|
||||
_basicUserName = settings.getString(BASIC_LOGIN, "")
|
||||
}
|
||||
|
||||
private fun refreshBasicPassword() {
|
||||
_basicPassword = settings.getString(BASIC_PASSWORD, "")
|
||||
}
|
||||
|
||||
private fun refreshArticleViewerEnabled() {
|
||||
_articleViewer = settings.getBoolean(PREFER_ARTICLE_VIEWER, true)
|
||||
}
|
||||
@ -378,8 +354,6 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
|
||||
fun refreshApiSettings() {
|
||||
refreshPassword()
|
||||
refreshUsername()
|
||||
refreshBasicUsername()
|
||||
refreshBasicPassword()
|
||||
refreshBaseUrl()
|
||||
refreshApiVersion()
|
||||
refreshPublicAccess()
|
||||
@ -413,17 +387,7 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
|
||||
login: String,
|
||||
password: String
|
||||
) {
|
||||
val regex = """\/\/(\D+):(\D+)@""".toRegex()
|
||||
val matchResult = regex.find(url)
|
||||
if (matchResult != null) {
|
||||
val (basicLogin, basicPassword) = matchResult.destructured
|
||||
settings.putString(BASIC_LOGIN, basicLogin)
|
||||
settings.putString(BASIC_PASSWORD, basicPassword)
|
||||
val urlWithoutBasicAuth = url.replace(regex, "//")
|
||||
settings.putString(BASE_URL, urlWithoutBasicAuth)
|
||||
} else {
|
||||
settings.putString(BASE_URL, url)
|
||||
}
|
||||
settings.putString(BASE_URL, url)
|
||||
settings.putString(LOGIN, login)
|
||||
settings.putString(PASSWORD, password)
|
||||
refreshApiSettings()
|
||||
@ -433,8 +397,6 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
|
||||
settings.remove(BASE_URL)
|
||||
settings.remove(LOGIN)
|
||||
settings.remove(PASSWORD)
|
||||
settings.remove(BASIC_LOGIN)
|
||||
settings.remove(BASIC_PASSWORD)
|
||||
refreshApiSettings()
|
||||
}
|
||||
|
||||
@ -478,10 +440,6 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
|
||||
|
||||
const val PASSWORD = "password"
|
||||
|
||||
const val BASIC_LOGIN = "basic_login"
|
||||
|
||||
const val BASIC_PASSWORD = "basic_password"
|
||||
|
||||
const val PREFER_ARTICLE_VIEWER = "prefer_article_viewer"
|
||||
|
||||
const val CARD_VIEW_ACTIVE = "card_view_active"
|
||||
@ -512,6 +470,7 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
|
||||
|
||||
const val PERIODIC_REFRESH_MINUTES = "periodic_refresh_minutes"
|
||||
|
||||
|
||||
const val INFINITE_LOADING = "infinite_loading"
|
||||
|
||||
const val ITEMS_CACHING = "items_caching"
|
||||
|
@ -12,25 +12,24 @@ fun TAG.toView(): SelfossModel.Tag =
|
||||
this.unread.toInt()
|
||||
)
|
||||
|
||||
fun SOURCE.toView(): SelfossModel.SourceDetail =
|
||||
SelfossModel.SourceDetail(
|
||||
fun SOURCE.toView(): SelfossModel.Source =
|
||||
SelfossModel.Source(
|
||||
this.id.toInt(),
|
||||
this.title,
|
||||
null,
|
||||
this.tags?.split(","),
|
||||
this.tags.split(","),
|
||||
this.spout,
|
||||
this.error,
|
||||
this.icon,
|
||||
if (this.url != null) SelfossModel.SourceParams(this.url) else null
|
||||
)
|
||||
|
||||
fun SelfossModel.SourceDetail.toEntity(): SOURCE =
|
||||
fun SelfossModel.Source.toEntity(): SOURCE =
|
||||
SOURCE(
|
||||
this.id.toString(),
|
||||
this.title.getHtmlDecoded(),
|
||||
this.tags?.joinToString(",").orEmpty(),
|
||||
this.spout.orEmpty(),
|
||||
this.error.orEmpty(),
|
||||
this.tags.joinToString(","),
|
||||
this.spout,
|
||||
this.error,
|
||||
this.icon.orEmpty(),
|
||||
this.params?.url
|
||||
)
|
||||
|
Reference in New Issue
Block a user