Compare commits
27 Commits
e1c64cef46
...
629ca01d99
Author | SHA1 | Date | |
---|---|---|---|
|
629ca01d99 | ||
|
c2d8681ce8 | ||
|
08f79cb148 | ||
e21906e70d | |||
|
9d2cc32bc9 | ||
|
d9d057c8dc | ||
|
1f3fa0c4a6 | ||
|
dea3def385 | ||
|
f72ef2f5d4 | ||
|
f28cb759df | ||
|
b9d69c3e64 | ||
|
c2a1c9eaac | ||
|
bf37209a15 | ||
|
2c558fe6fd | ||
|
ad88011454 | ||
|
559c17bc1d | ||
|
ab9c46f0eb | ||
|
aa799d2ca8 | ||
|
177c978474 | ||
|
39b9991413 | ||
|
b303f110f1 | ||
|
f851941a6a | ||
|
a313552976 | ||
|
6ac97ed3fe | ||
|
d583b937b7 | ||
|
15b9a2d935 | ||
|
5a8ce15961 |
28
.drone.yml
28
.drone.yml
@ -48,13 +48,14 @@ steps:
|
|||||||
commands:
|
commands:
|
||||||
- apt-get update && apt-get install -y git
|
- apt-get update && apt-get install -y git
|
||||||
- git fetch --tags -p
|
- git fetch --tags -p
|
||||||
|
- PREV=$(git describe --tags --abbrev=0)
|
||||||
|
- ./build.sh --publish --from-ci
|
||||||
|
- git remote add pushing https://$GITEA_USR:$GITEA_PASS@gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform.git
|
||||||
- VER=$(git describe --tags --abbrev=0)
|
- VER=$(git describe --tags --abbrev=0)
|
||||||
- CHANGELOG=$(git log $VER..HEAD --pretty="- %s")
|
- CHANGELOG=$(git log $PREV..HEAD --pretty="- %s")
|
||||||
- echo "**$VER**\n\n$CHANGELOG\n\n--------------------------------------------------------------------\n\n$(cat CHANGELOG.md)" > CHANGELOG.md
|
- echo "**$VER**\n\n$CHANGELOG\n\n--------------------------------------------------------------------\n\n$(cat CHANGELOG.md)" > CHANGELOG.md
|
||||||
- git add CHANGELOG.md
|
- git add CHANGELOG.md
|
||||||
- git commit -m "Changelog for $VER [CI SKIP]"
|
- git commit -m "Changelog for $VER [CI SKIP]"
|
||||||
- ./build.sh --publish --from-ci
|
|
||||||
- git remote add pushing https://$GITEA_USR:$GITEA_PASS@gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform.git
|
|
||||||
- git push pushing master
|
- git push pushing master
|
||||||
- git push pushing --tags
|
- git push pushing --tags
|
||||||
environment:
|
environment:
|
||||||
@ -136,6 +137,27 @@ steps:
|
|||||||
from_secret: giteaAPI
|
from_secret: giteaAPI
|
||||||
base_url: https://gitea.amine-louveau.fr
|
base_url: https://gitea.amine-louveau.fr
|
||||||
files: signed.apk
|
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:
|
trigger:
|
||||||
event:
|
event:
|
||||||
- tag
|
- tag
|
59
CHANGELOG.md
59
CHANGELOG.md
@ -1,3 +1,62 @@
|
|||||||
|
**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.
|
||||||
|
|
||||||
|
--------------------------------------------------------------------
|
||||||
|
|
||||||
|
**v123020491**
|
||||||
|
|
||||||
|
- fix: Fixed acra bug reporting.
|
||||||
|
|
||||||
|
--------------------------------------------------------------------
|
||||||
|
|
||||||
|
**v123010301**
|
||||||
|
|
||||||
|
- Chore: acra config.
|
||||||
|
|
||||||
|
--------------------------------------------------------------------
|
||||||
|
|
||||||
**v123010281**
|
**v123010281**
|
||||||
|
|
||||||
- improvement: Improve right to left support (#130) Co-authored-by: davidoskky <davidoskky@hidden.hidden> Co-committed-by: davidoskky <davidoskky@hidden.hidden>
|
- improvement: Improve right to left support (#130) Co-authored-by: davidoskky <davidoskky@hidden.hidden> Co-committed-by: davidoskky <davidoskky@hidden.hidden>
|
||||||
|
@ -28,7 +28,7 @@ fun gitVersion(): String {
|
|||||||
val maybeTagOfCurrentCommit = execWithOutput("git -C ../ describe --contains HEAD", true)
|
val maybeTagOfCurrentCommit = execWithOutput("git -C ../ describe --contains HEAD", true)
|
||||||
process = if (maybeTagOfCurrentCommit.isEmpty()) {
|
process = if (maybeTagOfCurrentCommit.isEmpty()) {
|
||||||
println("No tag on current commit. Will take the latest one.")
|
println("No tag on current commit. Will take the latest one.")
|
||||||
execWithOutput("git -C ../ for-each-ref refs/tags --sort=-authordate --format='%(refname:short)' --count=1")
|
execWithOutput("git -C ../ for-each-ref refs/tags --sort=-refname --format='%(refname:short)' --count=1")
|
||||||
} else {
|
} else {
|
||||||
println("Tag found on current commit")
|
println("Tag found on current commit")
|
||||||
execWithOutput("git -C ../ describe --contains HEAD")
|
execWithOutput("git -C ../ describe --contains HEAD")
|
||||||
@ -145,8 +145,8 @@ dependencies {
|
|||||||
implementation("com.amulyakhare:com.amulyakhare.textdrawable:1.0.1")
|
implementation("com.amulyakhare:com.amulyakhare.textdrawable:1.0.1")
|
||||||
|
|
||||||
// glide
|
// glide
|
||||||
kapt("com.github.bumptech.glide:compiler:4.14.2")
|
kapt("com.github.bumptech.glide:compiler:4.15.0")
|
||||||
implementation("com.github.bumptech.glide:okhttp3-integration:4.14.2")
|
implementation("com.github.bumptech.glide:okhttp3-integration:4.15.0")
|
||||||
|
|
||||||
// Themes
|
// Themes
|
||||||
implementation("com.github.rubensousa:floatingtoolbar:1.5.1")
|
implementation("com.github.rubensousa:floatingtoolbar:1.5.1")
|
||||||
|
@ -34,6 +34,7 @@ import org.kodein.di.*
|
|||||||
class MyApp : MultiDexApplication(), DIAware {
|
class MyApp : MultiDexApplication(), DIAware {
|
||||||
|
|
||||||
override val di by DI.lazy {
|
override val di by DI.lazy {
|
||||||
|
bind<AppSettingsService>() with singleton { AppSettingsService(ACRA.isACRASenderServiceProcess()) }
|
||||||
import(networkModule)
|
import(networkModule)
|
||||||
bind<DriverFactory>() with singleton { DriverFactory(applicationContext) }
|
bind<DriverFactory>() with singleton { DriverFactory(applicationContext) }
|
||||||
bind<ReaderForSelfossDB>() with singleton { ReaderForSelfossDB(driverFactory.createDriver()) }
|
bind<ReaderForSelfossDB>() with singleton { ReaderForSelfossDB(driverFactory.createDriver()) }
|
||||||
@ -130,8 +131,8 @@ class MyApp : MultiDexApplication(), DIAware {
|
|||||||
httpSender {
|
httpSender {
|
||||||
uri =
|
uri =
|
||||||
"https://bugs.amine-louveau.fr/report" /*best guess, you may need to adjust this*/
|
"https://bugs.amine-louveau.fr/report" /*best guess, you may need to adjust this*/
|
||||||
basicAuthLogin = "LMTlLZuazADohTCm"
|
basicAuthLogin = "qMEscjj89Gwt6cPR"
|
||||||
basicAuthPassword = "he6ghHp83F0PYPfh"
|
basicAuthPassword = "Yo58QFlGzFaWlBzP"
|
||||||
httpMethod = HttpSender.Method.POST
|
httpMethod = HttpSender.Method.POST
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,13 +49,13 @@ class SourcesActivity : AppCompatActivity(), DIAware {
|
|||||||
super.onResume()
|
super.onResume()
|
||||||
val mLayoutManager = LinearLayoutManager(this)
|
val mLayoutManager = LinearLayoutManager(this)
|
||||||
|
|
||||||
var items: ArrayList<SelfossModel.Source>
|
var items: ArrayList<SelfossModel.SourceDetail>
|
||||||
|
|
||||||
binding.recyclerView.setHasFixedSize(true)
|
binding.recyclerView.setHasFixedSize(true)
|
||||||
binding.recyclerView.layoutManager = mLayoutManager
|
binding.recyclerView.layoutManager = mLayoutManager
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
val response = repository.getSources()
|
val response = repository.getSourcesDetails()
|
||||||
if (response.isNotEmpty()) {
|
if (response.isNotEmpty()) {
|
||||||
items = response
|
items = response
|
||||||
val mAdapter = SourcesListAdapter(
|
val mAdapter = SourcesListAdapter(
|
||||||
|
@ -24,7 +24,7 @@ import org.kodein.di.instance
|
|||||||
|
|
||||||
class UpsertSourceActivity : AppCompatActivity(), DIAware {
|
class UpsertSourceActivity : AppCompatActivity(), DIAware {
|
||||||
|
|
||||||
private var existingSource: SelfossModel.Source? = null
|
private var existingSource: SelfossModel.SourceDetail? = null
|
||||||
private var mSpoutsValue: String? = null
|
private var mSpoutsValue: String? = null
|
||||||
|
|
||||||
private lateinit var binding: ActivityUpsertSourceBinding
|
private lateinit var binding: ActivityUpsertSourceBinding
|
||||||
@ -68,7 +68,7 @@ class UpsertSourceActivity : AppCompatActivity(), DIAware {
|
|||||||
|
|
||||||
private fun initFields(items: Map<String, SelfossModel.Spout>) {
|
private fun initFields(items: Map<String, SelfossModel.Spout>) {
|
||||||
binding.nameInput.setText(existingSource!!.title)
|
binding.nameInput.setText(existingSource!!.title)
|
||||||
binding.tags.setText(existingSource!!.tags.joinToString(", "))
|
binding.tags.setText(existingSource!!.tags?.joinToString(", "))
|
||||||
binding.sourceUri.setText(existingSource!!.params?.url)
|
binding.sourceUri.setText(existingSource!!.params?.url)
|
||||||
binding.spoutsSpinner.setSelection(items.keys.indexOf(existingSource!!.spout))
|
binding.spoutsSpinner.setSelection(items.keys.indexOf(existingSource!!.spout))
|
||||||
binding.progress.visibility = View.GONE
|
binding.progress.visibility = View.GONE
|
||||||
|
@ -31,7 +31,7 @@ import org.kodein.di.instance
|
|||||||
|
|
||||||
class SourcesListAdapter(
|
class SourcesListAdapter(
|
||||||
private val app: Activity,
|
private val app: Activity,
|
||||||
private val items: ArrayList<SelfossModel.Source>
|
private val items: ArrayList<SelfossModel.SourceDetail>
|
||||||
) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>(), DIAware {
|
) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>(), DIAware {
|
||||||
private val c: Context = app.baseContext
|
private val c: Context = app.baseContext
|
||||||
private val generator: ColorGenerator = ColorGenerator.MATERIAL
|
private val generator: ColorGenerator = ColorGenerator.MATERIAL
|
||||||
@ -61,7 +61,7 @@ class SourcesListAdapter(
|
|||||||
c.circularBitmapDrawable(itm.getIcon(repository.baseUrl), binding.itemImage)
|
c.circularBitmapDrawable(itm.getIcon(repository.baseUrl), binding.itemImage)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (itm.error.isNotBlank()) {
|
if (!itm.error.isNullOrBlank()) {
|
||||||
binding.errorText.visibility = View.VISIBLE
|
binding.errorText.visibility = View.VISIBLE
|
||||||
binding.errorText.text = itm.error
|
binding.errorText.text = itm.error
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.android.fragments
|
package bou.amine.apps.readerforselfossv2.android.fragments
|
||||||
|
|
||||||
|
import android.content.ActivityNotFoundException
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.content.res.TypedArray
|
import android.content.res.TypedArray
|
||||||
@ -150,17 +151,19 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
|
|
||||||
} catch (e: InflateException) {
|
} catch (e: InflateException) {
|
||||||
e.sendSilentlyWithAcraWithName("webview not available")
|
e.sendSilentlyWithAcraWithName("webview not available")
|
||||||
AlertDialog.Builder(requireContext())
|
if (context != null) {
|
||||||
.setMessage(requireContext().getString(R.string.webview_dialog_issue_message))
|
AlertDialog.Builder(requireContext())
|
||||||
.setTitle(requireContext().getString(R.string.webview_dialog_issue_title))
|
.setMessage(requireContext().getString(R.string.webview_dialog_issue_message))
|
||||||
.setPositiveButton(
|
.setTitle(requireContext().getString(R.string.webview_dialog_issue_title))
|
||||||
android.R.string.ok
|
.setPositiveButton(
|
||||||
) { _, _ ->
|
android.R.string.ok
|
||||||
appSettingsService.disableArticleViewer()
|
) { _, _ ->
|
||||||
requireActivity().finish()
|
appSettingsService.disableArticleViewer()
|
||||||
}
|
requireActivity().finish()
|
||||||
.create()
|
}
|
||||||
.show()
|
.create()
|
||||||
|
.show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return binding.root
|
return binding.root
|
||||||
@ -304,10 +307,16 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
binding.webcontent.webViewClient = object : WebViewClient() {
|
binding.webcontent.webViewClient = object : WebViewClient() {
|
||||||
@Deprecated("Deprecated in Java")
|
@Deprecated("Deprecated in Java")
|
||||||
override fun shouldOverrideUrlLoading(view: WebView?, url: String): Boolean {
|
override fun shouldOverrideUrlLoading(view: WebView?, url: String): Boolean {
|
||||||
if (binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
|
return if (context != null && binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
|
||||||
requireContext().startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
|
try {
|
||||||
|
requireContext().startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
|
||||||
|
} catch (e: ActivityNotFoundException) {
|
||||||
|
e.sendSilentlyWithAcraWithName("activityNotFound > $url")
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated("Deprecated in Java")
|
@Deprecated("Deprecated in Java")
|
||||||
@ -325,7 +334,7 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
getBitmapInputStream(image, Bitmap.CompressFormat.JPEG)
|
getBitmapInputStream(image, Bitmap.CompressFormat.JPEG)
|
||||||
)
|
)
|
||||||
} catch (e: ExecutionException) {
|
} catch (e: ExecutionException) {
|
||||||
e.sendSilentlyWithAcraWithName("shouldInterceptRequest > jpeg > $url")
|
// Do nothing
|
||||||
}
|
}
|
||||||
} else if (url.lowercase(Locale.US).contains(".png")) {
|
} else if (url.lowercase(Locale.US).contains(".png")) {
|
||||||
try {
|
try {
|
||||||
@ -337,7 +346,7 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
getBitmapInputStream(image, Bitmap.CompressFormat.PNG)
|
getBitmapInputStream(image, Bitmap.CompressFormat.PNG)
|
||||||
)
|
)
|
||||||
} catch (e: ExecutionException) {
|
} catch (e: ExecutionException) {
|
||||||
e.sendSilentlyWithAcraWithName("shouldInterceptRequest > png > $url")
|
// Do nothing
|
||||||
}
|
}
|
||||||
} else if (url.lowercase(Locale.US).contains(".webp")) {
|
} else if (url.lowercase(Locale.US).contains(".webp")) {
|
||||||
try {
|
try {
|
||||||
@ -349,7 +358,7 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
getBitmapInputStream(image, Bitmap.CompressFormat.WEBP)
|
getBitmapInputStream(image, Bitmap.CompressFormat.WEBP)
|
||||||
)
|
)
|
||||||
} catch (e: ExecutionException) {
|
} catch (e: ExecutionException) {
|
||||||
e.sendSilentlyWithAcraWithName("shouldInterceptRequest > webp > $url")
|
// Do nothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -359,74 +368,74 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun htmlToWebview() {
|
private fun htmlToWebview() {
|
||||||
|
if (context != null) {
|
||||||
val attrs: IntArray = intArrayOf(android.R.attr.fontFamily)
|
val attrs: IntArray = intArrayOf(android.R.attr.fontFamily)
|
||||||
val a: TypedArray = requireContext().obtainStyledAttributes(resId, attrs)
|
val a: TypedArray = requireContext().obtainStyledAttributes(resId, attrs)
|
||||||
|
|
||||||
|
|
||||||
binding.webcontent.settings.standardFontFamily = a.getString(0)
|
binding.webcontent.settings.standardFontFamily = a.getString(0)
|
||||||
binding.webcontent.visibility = View.VISIBLE
|
binding.webcontent.visibility = View.VISIBLE
|
||||||
|
|
||||||
val colorOnSurface = TypedValue()
|
val colorOnSurface = TypedValue()
|
||||||
requireContext().theme.resolveAttribute(R.attr.colorOnSurface, colorOnSurface, true)
|
requireContext().theme.resolveAttribute(R.attr.colorOnSurface, colorOnSurface, true)
|
||||||
|
|
||||||
val colorSurface = TypedValue()
|
val colorSurface = TypedValue()
|
||||||
requireContext().theme.resolveAttribute(R.attr.colorSurface, colorSurface, true)
|
requireContext().theme.resolveAttribute(R.attr.colorSurface, colorSurface, true)
|
||||||
|
|
||||||
binding.webcontent.settings.useWideViewPort = true
|
binding.webcontent.settings.useWideViewPort = true
|
||||||
binding.webcontent.settings.loadWithOverviewMode = true
|
binding.webcontent.settings.loadWithOverviewMode = true
|
||||||
binding.webcontent.settings.javaScriptEnabled = false
|
binding.webcontent.settings.javaScriptEnabled = false
|
||||||
|
|
||||||
handleImageLoading()
|
handleImageLoading()
|
||||||
|
|
||||||
val gestureDetector =
|
val gestureDetector =
|
||||||
GestureDetector(activity, object : GestureDetector.SimpleOnGestureListener() {
|
GestureDetector(activity, object : GestureDetector.SimpleOnGestureListener() {
|
||||||
override fun onSingleTapUp(e: MotionEvent): Boolean {
|
override fun onSingleTapUp(e: MotionEvent): Boolean {
|
||||||
return performClick()
|
return performClick()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
binding.webcontent.setOnTouchListener { _, event -> gestureDetector.onTouchEvent(event) }
|
binding.webcontent.setOnTouchListener { _, event -> gestureDetector.onTouchEvent(event) }
|
||||||
|
|
||||||
binding.webcontent.settings.layoutAlgorithm =
|
binding.webcontent.settings.layoutAlgorithm =
|
||||||
WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
|
WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
|
||||||
|
|
||||||
var baseUrl: String? = null
|
var baseUrl: String? = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val itemUrl = URL(url)
|
val itemUrl = URL(url)
|
||||||
baseUrl = itemUrl.protocol + "://" + itemUrl.host
|
baseUrl = itemUrl.protocol + "://" + itemUrl.host
|
||||||
} catch (e: MalformedURLException) {
|
} catch (e: MalformedURLException) {
|
||||||
e.sendSilentlyWithAcraWithName("htmlToWebview > item url")
|
e.sendSilentlyWithAcraWithName("htmlToWebview > $url")
|
||||||
}
|
}
|
||||||
|
|
||||||
val fontName = when (font) {
|
val fontName = when (font) {
|
||||||
getString(R.string.open_sans_font_id) -> "Open Sans"
|
getString(R.string.open_sans_font_id) -> "Open Sans"
|
||||||
getString(R.string.roboto_font_id) -> "Roboto"
|
getString(R.string.roboto_font_id) -> "Roboto"
|
||||||
getString(R.string.source_code_pro_font_id) -> "Source Code Pro"
|
getString(R.string.source_code_pro_font_id) -> "Source Code Pro"
|
||||||
else -> ""
|
else -> ""
|
||||||
}
|
}
|
||||||
|
|
||||||
val fontLinkAndStyle = if (font.isNotEmpty()) {
|
val fontLinkAndStyle = if (font.isNotEmpty()) {
|
||||||
"""<link href="https://fonts.googleapis.com/css?family=${
|
"""<link href="https://fonts.googleapis.com/css?family=${
|
||||||
fontName.replace(
|
fontName.replace(
|
||||||
" ",
|
" ",
|
||||||
"+"
|
"+"
|
||||||
)
|
)
|
||||||
}" rel="stylesheet">
|
}" rel="stylesheet">
|
||||||
|<style>
|
|<style>
|
||||||
| * {
|
| * {
|
||||||
| font-family: '$fontName';
|
| font-family: '$fontName';
|
||||||
| }
|
| }
|
||||||
|</style>
|
|</style>
|
||||||
""".trimMargin()
|
""".trimMargin()
|
||||||
} else {
|
} else {
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.webcontent.loadDataWithBaseURL(
|
binding.webcontent.loadDataWithBaseURL(
|
||||||
baseUrl,
|
baseUrl,
|
||||||
"""<html>
|
"""<html>
|
||||||
|<head>
|
|<head>
|
||||||
| <meta name="viewport" content="width=device-width, initial-scale=1">
|
| <meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
| <style>
|
| <style>
|
||||||
@ -438,11 +447,11 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
| }
|
| }
|
||||||
| a {
|
| a {
|
||||||
| color: ${
|
| color: ${
|
||||||
String.format(
|
String.format(
|
||||||
"#%06X",
|
"#%06X",
|
||||||
0xFFFFFF and resources.getColor(R.color.colorAccent)
|
0xFFFFFF and resources.getColor(R.color.colorAccent)
|
||||||
)
|
)
|
||||||
} !important;
|
} !important;
|
||||||
| }
|
| }
|
||||||
| *:not(a) {
|
| *:not(a) {
|
||||||
| color: ${String.format("#%06X", 0xFFFFFF and colorOnSurface.data)};
|
| color: ${String.format("#%06X", 0xFFFFFF and colorOnSurface.data)};
|
||||||
@ -454,25 +463,25 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
| overflow:hidden;
|
| overflow:hidden;
|
||||||
| line-height: 1.5em;
|
| line-height: 1.5em;
|
||||||
| background-color: ${
|
| background-color: ${
|
||||||
String.format(
|
String.format(
|
||||||
"#%06X",
|
"#%06X",
|
||||||
0xFFFFFF and colorSurface.data
|
0xFFFFFF and colorSurface.data
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
| }
|
| }
|
||||||
| body, html {
|
| body, html {
|
||||||
| background-color: ${
|
| background-color: ${
|
||||||
String.format(
|
String.format(
|
||||||
"#%06X",
|
"#%06X",
|
||||||
0xFFFFFF and colorSurface.data
|
0xFFFFFF and colorSurface.data
|
||||||
)
|
)
|
||||||
} !important;
|
} !important;
|
||||||
| border-color: ${
|
| border-color: ${
|
||||||
String.format(
|
String.format(
|
||||||
"#%06X",
|
"#%06X",
|
||||||
0xFFFFFF and colorSurface.data
|
0xFFFFFF and colorSurface.data
|
||||||
)
|
)
|
||||||
} !important;
|
} !important;
|
||||||
| padding: 0 !important;
|
| padding: 0 !important;
|
||||||
| margin: 0 !important;
|
| margin: 0 !important;
|
||||||
| }
|
| }
|
||||||
@ -483,11 +492,11 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
| white-space: pre-wrap;
|
| white-space: pre-wrap;
|
||||||
| width:100%;
|
| width:100%;
|
||||||
| background-color: ${
|
| background-color: ${
|
||||||
String.format(
|
String.format(
|
||||||
"#%06X",
|
"#%06X",
|
||||||
0xFFFFFF and colorSurface.data
|
0xFFFFFF and colorSurface.data
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
| }
|
| }
|
||||||
| </style>
|
| </style>
|
||||||
| $fontLinkAndStyle
|
| $fontLinkAndStyle
|
||||||
@ -495,10 +504,11 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
|<body>
|
|<body>
|
||||||
| $contentText
|
| $contentText
|
||||||
|</body>""".trimMargin(),
|
|</body>""".trimMargin(),
|
||||||
"text/html",
|
"text/html",
|
||||||
"utf-8",
|
"utf-8",
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun scrollDown() {
|
fun scrollDown() {
|
||||||
|
@ -20,10 +20,8 @@ import bou.amine.apps.readerforselfossv2.repository.Repository
|
|||||||
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
|
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
|
||||||
import bou.amine.apps.readerforselfossv2.utils.getIcon
|
import bou.amine.apps.readerforselfossv2.utils.getIcon
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.load.DataSource
|
import com.bumptech.glide.request.target.ViewTarget
|
||||||
import com.bumptech.glide.load.engine.GlideException
|
import com.bumptech.glide.request.transition.Transition
|
||||||
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.bottomsheet.BottomSheetDialogFragment
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@ -84,37 +82,25 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
|
|||||||
) {
|
) {
|
||||||
val sourceGroup = binding.sourcesGroup
|
val sourceGroup = binding.sourcesGroup
|
||||||
|
|
||||||
repository.getSources().forEach { source ->
|
repository.getSourcesDetailsOrStats().forEach { source ->
|
||||||
val c = Chip(context)
|
val c = Chip(context)
|
||||||
c.ellipsize = TextUtils.TruncateAt.END
|
c.ellipsize = TextUtils.TruncateAt.END
|
||||||
|
|
||||||
Glide.with(context)
|
Glide.with(context)
|
||||||
.load(source.getIcon(repository.baseUrl))
|
.load(source.getIcon(repository.baseUrl))
|
||||||
.listener(object : RequestListener<Drawable?> {
|
.into(object : ViewTarget<Chip?, Drawable?>(c) {
|
||||||
override fun onLoadFailed(
|
|
||||||
e: GlideException?,
|
|
||||||
model: Any?,
|
|
||||||
target: Target<Drawable?>?,
|
|
||||||
isFirstResource: Boolean
|
|
||||||
): Boolean {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResourceReady(
|
override fun onResourceReady(
|
||||||
resource: Drawable?,
|
resource: Drawable,
|
||||||
model: Any?,
|
transition: Transition<in Drawable?>?
|
||||||
target: Target<Drawable?>?,
|
) {
|
||||||
dataSource: DataSource?,
|
|
||||||
isFirstResource: Boolean
|
|
||||||
): Boolean {
|
|
||||||
try {
|
try {
|
||||||
c.chipIcon = resource
|
c.chipIcon = resource
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.sendSilentlyWithAcraWithName("sources > onResourceReady")
|
e.sendSilentlyWithAcraWithName("sources > onResourceReady")
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
}).preload()
|
|
||||||
|
})
|
||||||
|
|
||||||
c.text = source.title.getHtmlDecoded()
|
c.text = source.title.getHtmlDecoded()
|
||||||
|
|
||||||
@ -141,9 +127,9 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
|
|||||||
selectedChip = c
|
selectedChip = c
|
||||||
}
|
}
|
||||||
|
|
||||||
c.isEnabled = source.error.isBlank()
|
c.isEnabled = source.error.isNullOrBlank()
|
||||||
|
|
||||||
if (source.error.isNotBlank() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (!source.error.isNullOrBlank() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
c.tooltipText = source.error
|
c.tooltipText = source.error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -300,9 +300,10 @@ class RepositoryTest {
|
|||||||
every { appSettingsService.isItemCachingEnabled() } returns true
|
every { appSettingsService.isItemCachingEnabled() } returns true
|
||||||
|
|
||||||
initializeRepository(MutableStateFlow(false))
|
initializeRepository(MutableStateFlow(false))
|
||||||
repository.setSourceFilter(SelfossModel.Source(
|
repository.setSourceFilter(SelfossModel.SourceDetail(
|
||||||
1,
|
1,
|
||||||
"Test",
|
"Test",
|
||||||
|
null,
|
||||||
listOf("tags"),
|
listOf("tags"),
|
||||||
SPOUT,
|
SPOUT,
|
||||||
"",
|
"",
|
||||||
@ -609,30 +610,32 @@ class RepositoryTest {
|
|||||||
fun get_sources() {
|
fun get_sources() {
|
||||||
val (sources, sourcesDB) = prepareSources()
|
val (sources, sourcesDB) = prepareSources()
|
||||||
initializeRepository()
|
initializeRepository()
|
||||||
var testSources: List<SelfossModel.Source>?
|
var testSources: List<SelfossModel.Source>
|
||||||
runBlocking {
|
runBlocking {
|
||||||
testSources = repository.getSources()
|
testSources = repository.getSourcesDetails()
|
||||||
}
|
}
|
||||||
|
|
||||||
assertSame(sources, testSources)
|
assertEquals(sources, testSources)
|
||||||
assertNotEquals(sourcesDB.map { it.toView() }, testSources)
|
assertNotEquals(sourcesDB.map { it.toView() }, testSources)
|
||||||
coVerify(exactly = 1) { api.sources() }
|
coVerify(exactly = 1) { api.sourcesDetailed() }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun prepareSources(): Pair<ArrayList<SelfossModel.Source>, List<SOURCE>> {
|
private fun prepareSources(): Pair<ArrayList<SelfossModel.SourceDetail>, List<SOURCE>> {
|
||||||
val sources = arrayListOf(
|
val sources = arrayListOf(
|
||||||
SelfossModel.Source(
|
SelfossModel.SourceDetail(
|
||||||
1,
|
1,
|
||||||
"First source",
|
"First source",
|
||||||
|
null,
|
||||||
listOf("Test", "second"),
|
listOf("Test", "second"),
|
||||||
SPOUT,
|
SPOUT,
|
||||||
"",
|
"",
|
||||||
IMAGE_URL_2,
|
IMAGE_URL_2,
|
||||||
SelfossModel.SourceParams("url")
|
SelfossModel.SourceParams("url")
|
||||||
),
|
),
|
||||||
SelfossModel.Source(
|
SelfossModel.SourceDetail(
|
||||||
2,
|
2,
|
||||||
"Second source",
|
"Second source",
|
||||||
|
null,
|
||||||
listOf("second"),
|
listOf("second"),
|
||||||
SPOUT,
|
SPOUT,
|
||||||
"",
|
"",
|
||||||
@ -661,7 +664,7 @@ class RepositoryTest {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
coEvery { api.sources() } returns StatusAndData(success = true, data = sources)
|
coEvery { api.sourcesDetailed() } returns StatusAndData(success = true, data = sources)
|
||||||
every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB
|
every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB
|
||||||
return Pair(sources, sourcesDB)
|
return Pair(sources, sourcesDB)
|
||||||
}
|
}
|
||||||
@ -675,13 +678,13 @@ class RepositoryTest {
|
|||||||
initializeRepository()
|
initializeRepository()
|
||||||
var testSources: List<SelfossModel.Source>?
|
var testSources: List<SelfossModel.Source>?
|
||||||
runBlocking {
|
runBlocking {
|
||||||
testSources = repository.getSources()
|
testSources = repository.getSourcesDetails()
|
||||||
// Sources will be fetched from the database on the second call, thus testSources != sources
|
// Sources will be fetched from the database on the second call, thus testSources != sources
|
||||||
testSources = repository.getSources()
|
testSources = repository.getSourcesDetails()
|
||||||
}
|
}
|
||||||
|
|
||||||
coVerify(exactly = 1) { api.sources() }
|
coVerify(exactly = 1) { api.sourcesDetailed() }
|
||||||
assertNotSame(sources, testSources)
|
assertNotEquals(sources, testSources)
|
||||||
assertEquals(sourcesDB.map { it.toView() }, testSources)
|
assertEquals(sourcesDB.map { it.toView() }, testSources)
|
||||||
verify(atLeast = 1) { db.sourcesQueries.sources().executeAsList() }
|
verify(atLeast = 1) { db.sourcesQueries.sources().executeAsList() }
|
||||||
}
|
}
|
||||||
@ -693,13 +696,13 @@ class RepositoryTest {
|
|||||||
every { appSettingsService.isUpdateSourcesEnabled() } returns true
|
every { appSettingsService.isUpdateSourcesEnabled() } returns true
|
||||||
every { appSettingsService.isItemCachingEnabled() } returns false
|
every { appSettingsService.isItemCachingEnabled() } returns false
|
||||||
initializeRepository()
|
initializeRepository()
|
||||||
var testSources: List<SelfossModel.Source>?
|
var testSources: List<SelfossModel.Source>
|
||||||
runBlocking {
|
runBlocking {
|
||||||
testSources = repository.getSources()
|
testSources = repository.getSourcesDetails()
|
||||||
}
|
}
|
||||||
|
|
||||||
assertSame(sources, testSources)
|
assertEquals(sources, testSources)
|
||||||
coVerify(exactly = 1) { api.sources() }
|
coVerify(exactly = 1) { api.sourcesDetailed() }
|
||||||
verify(exactly = 0) { db.sourcesQueries }
|
verify(exactly = 0) { db.sourcesQueries }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -710,13 +713,13 @@ class RepositoryTest {
|
|||||||
every { appSettingsService.isUpdateSourcesEnabled() } returns false
|
every { appSettingsService.isUpdateSourcesEnabled() } returns false
|
||||||
every { appSettingsService.isItemCachingEnabled() } returns false
|
every { appSettingsService.isItemCachingEnabled() } returns false
|
||||||
initializeRepository()
|
initializeRepository()
|
||||||
var testSources: List<SelfossModel.Source>?
|
var testSources: List<SelfossModel.Source>
|
||||||
runBlocking {
|
runBlocking {
|
||||||
testSources = repository.getSources()
|
testSources = repository.getSourcesDetails()
|
||||||
}
|
}
|
||||||
|
|
||||||
assertSame(sources, testSources)
|
assertEquals(sources, testSources)
|
||||||
coVerify(exactly = 1) { api.sources() }
|
coVerify(exactly = 1) { api.sourcesDetailed() }
|
||||||
verify(atLeast = 1) { db.sourcesQueries }
|
verify(atLeast = 1) { db.sourcesQueries }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -724,13 +727,13 @@ class RepositoryTest {
|
|||||||
fun get_sources_without_connection() {
|
fun get_sources_without_connection() {
|
||||||
val (_, sourcesDB) = prepareSources()
|
val (_, sourcesDB) = prepareSources()
|
||||||
initializeRepository(MutableStateFlow(false))
|
initializeRepository(MutableStateFlow(false))
|
||||||
var testSources: List<SelfossModel.Source>?
|
var testSources: List<SelfossModel.Source>
|
||||||
runBlocking {
|
runBlocking {
|
||||||
testSources = repository.getSources()
|
testSources = repository.getSourcesDetails()
|
||||||
}
|
}
|
||||||
|
|
||||||
assertEquals(sourcesDB.map { it.toView() }, testSources)
|
assertEquals(sourcesDB.map { it.toView() }, testSources)
|
||||||
coVerify(exactly = 0) { api.sources() }
|
coVerify(exactly = 0) { api.sourcesDetailed() }
|
||||||
verify(atLeast = 1) { db.sourcesQueries.sources().executeAsList() }
|
verify(atLeast = 1) { db.sourcesQueries.sources().executeAsList() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -741,13 +744,13 @@ class RepositoryTest {
|
|||||||
every { appSettingsService.isItemCachingEnabled() } returns false
|
every { appSettingsService.isItemCachingEnabled() } returns false
|
||||||
every { appSettingsService.isUpdateSourcesEnabled() } returns true
|
every { appSettingsService.isUpdateSourcesEnabled() } returns true
|
||||||
initializeRepository(MutableStateFlow(false))
|
initializeRepository(MutableStateFlow(false))
|
||||||
var testSources: List<SelfossModel.Source>?
|
var testSources: List<SelfossModel.Source>
|
||||||
runBlocking {
|
runBlocking {
|
||||||
testSources = repository.getSources()
|
testSources = repository.getSourcesDetails()
|
||||||
}
|
}
|
||||||
|
|
||||||
assertEquals(emptyList<SelfossModel.Source>(), testSources)
|
assertEquals(emptyList<SelfossModel.Source>(), testSources)
|
||||||
coVerify(exactly = 0) { api.sources() }
|
coVerify(exactly = 0) { api.sourcesDetailed() }
|
||||||
verify(exactly = 0) { db.sourcesQueries.sources().executeAsList() }
|
verify(exactly = 0) { db.sourcesQueries.sources().executeAsList() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -758,13 +761,13 @@ class RepositoryTest {
|
|||||||
every { appSettingsService.isItemCachingEnabled() } returns true
|
every { appSettingsService.isItemCachingEnabled() } returns true
|
||||||
every { appSettingsService.isUpdateSourcesEnabled() } returns false
|
every { appSettingsService.isUpdateSourcesEnabled() } returns false
|
||||||
initializeRepository(MutableStateFlow(false))
|
initializeRepository(MutableStateFlow(false))
|
||||||
var testSources: List<SelfossModel.Source>?
|
var testSources: List<SelfossModel.Source>
|
||||||
runBlocking {
|
runBlocking {
|
||||||
testSources = repository.getSources()
|
testSources = repository.getSourcesDetails()
|
||||||
}
|
}
|
||||||
|
|
||||||
assertEquals(sourcesDB.map { it.toView() }, testSources)
|
assertEquals(sourcesDB.map { it.toView() }, testSources)
|
||||||
coVerify(exactly = 0) { api.sources() }
|
coVerify(exactly = 0) { api.sourcesDetailed() }
|
||||||
verify(atLeast = 1) { db.sourcesQueries.sources().executeAsList() }
|
verify(atLeast = 1) { db.sourcesQueries.sources().executeAsList() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -775,13 +778,13 @@ class RepositoryTest {
|
|||||||
every { appSettingsService.isItemCachingEnabled() } returns false
|
every { appSettingsService.isItemCachingEnabled() } returns false
|
||||||
every { appSettingsService.isUpdateSourcesEnabled() } returns false
|
every { appSettingsService.isUpdateSourcesEnabled() } returns false
|
||||||
initializeRepository(MutableStateFlow(false))
|
initializeRepository(MutableStateFlow(false))
|
||||||
var testSources: List<SelfossModel.Source>?
|
var testSources: List<SelfossModel.Source>
|
||||||
runBlocking {
|
runBlocking {
|
||||||
testSources = repository.getSources()
|
testSources = repository.getSourcesDetails()
|
||||||
}
|
}
|
||||||
|
|
||||||
assertEquals(sourcesDB.map { it.toView() }, testSources)
|
assertEquals(sourcesDB.map { it.toView() }, testSources)
|
||||||
coVerify(exactly = 0) { api.sources() }
|
coVerify(exactly = 0) { api.sourcesDetailed() }
|
||||||
verify(atLeast = 1) { db.sourcesQueries.sources().executeAsList() }
|
verify(atLeast = 1) { db.sourcesQueries.sources().executeAsList() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1102,9 +1105,10 @@ class RepositoryTest {
|
|||||||
private fun prepareSearch() {
|
private fun prepareSearch() {
|
||||||
repository.setTagFilter(SelfossModel.Tag("Tag", "read", 0))
|
repository.setTagFilter(SelfossModel.Tag("Tag", "read", 0))
|
||||||
repository.setSourceFilter(
|
repository.setSourceFilter(
|
||||||
SelfossModel.Source(
|
SelfossModel.SourceDetail(
|
||||||
1,
|
1,
|
||||||
"First source",
|
"First source",
|
||||||
|
5,
|
||||||
listOf("Test", "second"),
|
listOf("Test", "second"),
|
||||||
SPOUT,
|
SPOUT,
|
||||||
"",
|
"",
|
||||||
|
@ -9,7 +9,6 @@ import org.kodein.di.instance
|
|||||||
import org.kodein.di.singleton
|
import org.kodein.di.singleton
|
||||||
|
|
||||||
val networkModule by DI.Module {
|
val networkModule by DI.Module {
|
||||||
bind<AppSettingsService>() with singleton { AppSettingsService() }
|
|
||||||
bind<SelfossApi>() with singleton { SelfossApi(instance()) }
|
bind<SelfossApi>() with singleton { SelfossApi(instance()) }
|
||||||
bind<MercuryApi>() with singleton { MercuryApi() }
|
bind<MercuryApi>() with singleton { MercuryApi() }
|
||||||
}
|
}
|
@ -24,8 +24,8 @@ class SelfossModel {
|
|||||||
@Serializable
|
@Serializable
|
||||||
class Stats(
|
class Stats(
|
||||||
val total: Int,
|
val total: Int,
|
||||||
val unread: Int,
|
val unread: Int?,
|
||||||
val starred: Int
|
val starred: Int?
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@ -63,20 +63,39 @@ class SelfossModel {
|
|||||||
fun isPublicModeEnabled() = publicMode ?: false
|
fun isPublicModeEnabled() = publicMode ?: false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Source {
|
||||||
|
val id: Int
|
||||||
|
var title: String
|
||||||
|
var unread: Int?
|
||||||
|
var error: String?
|
||||||
|
var icon: String?
|
||||||
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Source(
|
data class SourceStats(
|
||||||
val id: Int,
|
override val id: Int,
|
||||||
val title: String,
|
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,
|
||||||
@Serializable(with = TagsListSerializer::class)
|
@Serializable(with = TagsListSerializer::class)
|
||||||
val tags: List<String>,
|
var tags: List<String>?,
|
||||||
val spout: String,
|
var spout: String?,
|
||||||
val error: String,
|
override var error: String?,
|
||||||
val icon: String?,
|
override var icon: String?,
|
||||||
val params: SourceParams?
|
var params: SourceParams?
|
||||||
)
|
) : Source
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class SourceParams(
|
data class SourceParams(
|
||||||
val url: String
|
val url: String? = null
|
||||||
)
|
)
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Item(
|
data class Item(
|
||||||
|
@ -44,11 +44,11 @@ class Repository(
|
|||||||
private val _badgeStarred = MutableStateFlow(0)
|
private val _badgeStarred = MutableStateFlow(0)
|
||||||
val badgeStarred = _badgeStarred.asStateFlow()
|
val badgeStarred = _badgeStarred.asStateFlow()
|
||||||
|
|
||||||
private var fetchedSources = false
|
|
||||||
private var fetchedTags = false
|
private var fetchedTags = false
|
||||||
|
private var fetchedSources = false
|
||||||
|
|
||||||
private var _readerItems = ArrayList<SelfossModel.Item>()
|
private var _readerItems = ArrayList<SelfossModel.Item>()
|
||||||
private var _selectedSource: SelfossModel.Source? = null
|
private var _selectedSource: SelfossModel.SourceDetail? = null
|
||||||
|
|
||||||
suspend fun getNewerItems(): ArrayList<SelfossModel.Item> {
|
suspend fun getNewerItems(): ArrayList<SelfossModel.Item> {
|
||||||
var fetchedItems: StatusAndData<List<SelfossModel.Item>> = StatusAndData.error()
|
var fetchedItems: StatusAndData<List<SelfossModel.Item>> = StatusAndData.error()
|
||||||
@ -132,9 +132,9 @@ class Repository(
|
|||||||
if (isNetworkAvailable()) {
|
if (isNetworkAvailable()) {
|
||||||
val response = api.stats()
|
val response = api.stats()
|
||||||
if (response.success && response.data != null) {
|
if (response.success && response.data != null) {
|
||||||
_badgeUnread.value = response.data.unread
|
_badgeUnread.value = response.data.unread ?: 0
|
||||||
_badgeAll.value = response.data.total
|
_badgeAll.value = response.data.total
|
||||||
_badgeStarred.value = response.data.starred
|
_badgeStarred.value = response.data.starred ?: 0
|
||||||
success = true
|
success = true
|
||||||
}
|
}
|
||||||
} else if (appSettingsService.isItemCachingEnabled()) {
|
} else if (appSettingsService.isItemCachingEnabled()) {
|
||||||
@ -180,23 +180,46 @@ class Repository(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getSources(): ArrayList<SelfossModel.Source> {
|
suspend fun getSourcesDetailsOrStats(): ArrayList<SelfossModel.Source> {
|
||||||
|
var sources = ArrayList<SelfossModel.Source>()
|
||||||
val isDatabaseEnabled =
|
val isDatabaseEnabled =
|
||||||
appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled()
|
appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled()
|
||||||
return if (isNetworkAvailable() && !fetchedSources) {
|
val shouldFetch = if (!appSettingsService.isUpdateSourcesEnabled()) !fetchedSources else true
|
||||||
val apiSources = api.sources()
|
if (shouldFetch && isNetworkAvailable()) {
|
||||||
if (apiSources.success && apiSources.data != null && isDatabaseEnabled) {
|
if (appSettingsService.getPublicAccess()) {
|
||||||
resetDBSourcesWithData(apiSources.data)
|
val apiSources = api.sourcesStats()
|
||||||
if (!appSettingsService.isUpdateSourcesEnabled()) {
|
if (apiSources.success && apiSources.data != null) {
|
||||||
fetchedSources = true
|
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) {
|
} else if (isDatabaseEnabled) {
|
||||||
ArrayList(getDBSources().map { it.toView() })
|
sources = getDBSources().map { it.toView() } as ArrayList<SelfossModel.SourceDetail>
|
||||||
} else {
|
|
||||||
ArrayList()
|
|
||||||
}
|
}
|
||||||
|
return sources
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun markAsRead(item: SelfossModel.Item): Boolean {
|
suspend fun markAsRead(item: SelfossModel.Item): Boolean {
|
||||||
@ -482,7 +505,7 @@ class Repository(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resetDBSourcesWithData(sources: List<SelfossModel.Source>) {
|
private fun resetDBSourcesWithData(sources: List<SelfossModel.SourceDetail>) {
|
||||||
db.sourcesQueries.deleteAllSources()
|
db.sourcesQueries.deleteAllSources()
|
||||||
|
|
||||||
db.sourcesQueries.transaction {
|
db.sourcesQueries.transaction {
|
||||||
@ -592,7 +615,7 @@ class Repository(
|
|||||||
ReaderForSelfossDB.Schema.migrate(driverFactory.createDriver(), 0, 1)
|
ReaderForSelfossDB.Schema.migrate(driverFactory.createDriver(), 0, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setSelectedSource(source: SelfossModel.Source) {
|
fun setSelectedSource(source: SelfossModel.SourceDetail) {
|
||||||
_selectedSource = source
|
_selectedSource = source
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -600,7 +623,7 @@ class Repository(
|
|||||||
_selectedSource = null
|
_selectedSource = null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSelectedSource(): SelfossModel.Source? {
|
fun getSelectedSource(): SelfossModel.SourceDetail? {
|
||||||
return _selectedSource
|
return _selectedSource
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -183,7 +183,15 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
suspend fun sources(): StatusAndData<ArrayList<SelfossModel.Source>> =
|
suspend fun sourcesStats(): StatusAndData<ArrayList<SelfossModel.SourceStats>> =
|
||||||
|
bodyOrFailure(client.tryToGet(url("/sources/stats")) {
|
||||||
|
if (!shouldHavePostLogin()) {
|
||||||
|
parameter("username", appSettingsService.getUserName())
|
||||||
|
parameter("password", appSettingsService.getPassword())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
suspend fun sourcesDetailed(): StatusAndData<ArrayList<SelfossModel.SourceDetail>> =
|
||||||
bodyOrFailure(client.tryToGet(url("/sources/list")) {
|
bodyOrFailure(client.tryToGet(url("/sources/list")) {
|
||||||
if (!shouldHavePostLogin()) {
|
if (!shouldHavePostLogin()) {
|
||||||
parameter("username", appSettingsService.getUserName())
|
parameter("username", appSettingsService.getUserName())
|
||||||
|
@ -0,0 +1,70 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.service
|
||||||
|
|
||||||
|
import com.russhwolf.settings.Settings
|
||||||
|
|
||||||
|
// This will be used in ACRA process. For now, it does nothing.
|
||||||
|
// This is to fix ACRA not sending reports anymore.
|
||||||
|
// See https://www.acra.ch/docs/Troubleshooting-Guide#applicationoncreate
|
||||||
|
class ACRASettings : Settings {
|
||||||
|
override val keys: Set<String> = emptySet()
|
||||||
|
override val size: Int = 0
|
||||||
|
|
||||||
|
override fun clear() {
|
||||||
|
// Nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getBoolean(key: String, defaultValue: Boolean): Boolean = false
|
||||||
|
|
||||||
|
override fun getBooleanOrNull(key: String): Boolean? = null
|
||||||
|
|
||||||
|
override fun getDouble(key: String, defaultValue: Double): Double = 0.0
|
||||||
|
|
||||||
|
override fun getDoubleOrNull(key: String): Double? = null
|
||||||
|
|
||||||
|
override fun getFloat(key: String, defaultValue: Float): Float = 0.0F
|
||||||
|
|
||||||
|
override fun getFloatOrNull(key: String): Float? = null
|
||||||
|
|
||||||
|
override fun getInt(key: String, defaultValue: Int): Int = 0
|
||||||
|
|
||||||
|
override fun getIntOrNull(key: String): Int? = null
|
||||||
|
|
||||||
|
override fun getLong(key: String, defaultValue: Long): Long = 0
|
||||||
|
|
||||||
|
override fun getLongOrNull(key: String): Long? = null
|
||||||
|
|
||||||
|
override fun getString(key: String, defaultValue: String): String = "0"
|
||||||
|
|
||||||
|
override fun getStringOrNull(key: String): String? = null
|
||||||
|
|
||||||
|
override fun hasKey(key: String): Boolean = false
|
||||||
|
|
||||||
|
override fun putBoolean(key: String, value: Boolean) {
|
||||||
|
// Nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun putDouble(key: String, value: Double) {
|
||||||
|
// Nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun putFloat(key: String, value: Float) {
|
||||||
|
// Nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun putInt(key: String, value: Int) {
|
||||||
|
// Nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun putLong(key: String, value: Long) {
|
||||||
|
// Nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun putString(key: String, value: String) {
|
||||||
|
// Nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun remove(key: String) {
|
||||||
|
// Nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -2,8 +2,8 @@ package bou.amine.apps.readerforselfossv2.service
|
|||||||
|
|
||||||
import com.russhwolf.settings.Settings
|
import com.russhwolf.settings.Settings
|
||||||
|
|
||||||
class AppSettingsService {
|
class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
|
||||||
val settings: Settings = Settings()
|
val settings: Settings = if (acraSenderServiceProcess) { ACRASettings() } else { Settings() }
|
||||||
|
|
||||||
// Api related
|
// Api related
|
||||||
private var _apiVersion: Int = -1
|
private var _apiVersion: Int = -1
|
||||||
|
@ -12,24 +12,25 @@ fun TAG.toView(): SelfossModel.Tag =
|
|||||||
this.unread.toInt()
|
this.unread.toInt()
|
||||||
)
|
)
|
||||||
|
|
||||||
fun SOURCE.toView(): SelfossModel.Source =
|
fun SOURCE.toView(): SelfossModel.SourceDetail =
|
||||||
SelfossModel.Source(
|
SelfossModel.SourceDetail(
|
||||||
this.id.toInt(),
|
this.id.toInt(),
|
||||||
this.title,
|
this.title,
|
||||||
this.tags.split(","),
|
null,
|
||||||
|
this.tags?.split(","),
|
||||||
this.spout,
|
this.spout,
|
||||||
this.error,
|
this.error,
|
||||||
this.icon,
|
this.icon,
|
||||||
if (this.url != null) SelfossModel.SourceParams(this.url) else null
|
if (this.url != null) SelfossModel.SourceParams(this.url) else null
|
||||||
)
|
)
|
||||||
|
|
||||||
fun SelfossModel.Source.toEntity(): SOURCE =
|
fun SelfossModel.SourceDetail.toEntity(): SOURCE =
|
||||||
SOURCE(
|
SOURCE(
|
||||||
this.id.toString(),
|
this.id.toString(),
|
||||||
this.title.getHtmlDecoded(),
|
this.title.getHtmlDecoded(),
|
||||||
this.tags.joinToString(","),
|
this.tags?.joinToString(",").orEmpty(),
|
||||||
this.spout,
|
this.spout.orEmpty(),
|
||||||
this.error,
|
this.error.orEmpty(),
|
||||||
this.icon.orEmpty(),
|
this.icon.orEmpty(),
|
||||||
this.params?.url
|
this.params?.url
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user