Compare commits

..

26 Commits

Author SHA1 Message Date
AmineB 236e1cca90 Fix recycleview article positions (#163)
Reviewed-on: #163
Reviewed-by: Amine Bouabdallaoui <amineb@hidden.hidden>
2024-11-20 09:32:29 +01:00
davidoskky 3a33cb4510 Provide method to update items in the home
Removed in the previous commit, the item adapter accepts a method to update the articles list in the home page.
Renamed the method to make its objective more clear.
Removed a debugging log.
Reverted change to function name.
2024-11-20 01:28:47 +01:00
davidoskky 0bf9ca9a49 Fix recycleview article positions
The articles were being opened by setting a click listener on the binding.
In card view this was being done through a function and as such it used the overall viewbind of the view rather than the binding of the item viewholder.
Now the functions are more explicit to avoid future errors.
Pulled up a few members from ItemCardAdapter and ItemListAdapter to ItemAdapter.
2024-11-19 01:50:58 +01:00
aminecmi 61e0087894 Changelog for v124041081 [CI SKIP] 2024-04-17 10:57:12 +00:00
aminecmi 1ec05d9913 chore: comment.
continuous-integration/drone/push Build is passing
continuous-integration/drone/promote/production Build is passing
continuous-integration/drone/tag Build is passing
2024-04-17 12:39:45 +02:00
aminecmi 859bd91bbb fix: Last time fixing the parsing date hack before moving it to os version. 2024-04-17 12:22:33 +02:00
aminecmi 204b736c53 Changelog for v124030731 [CI SKIP] 2024-03-13 19:51:14 +00:00
aminecmi f24609c143 fix: Basic auth and password can have non whitspace characters. Fixes 142.
continuous-integration/drone/push Build is passing
continuous-integration/drone/promote/production Build is passing
2024-03-13 20:31:24 +01:00
aminecmi b94d7dc537 Changelog for v124020451 [CI SKIP] 2024-02-14 19:54:05 +00:00
aminecmi 41910cc4cd fix: Fixed handling of position in card adapter.
continuous-integration/drone/push Build is passing
continuous-integration/drone/promote/production Build is passing
2024-02-13 21:31:02 +01:00
aminecmi db166ca9d4 Changelog for v124010301 [CI SKIP] 2024-01-30 19:36:37 +00:00
aminecmi db0d5a4a85 fix: This may fix the oom errors.
continuous-integration/drone/push Build is passing
continuous-integration/drone/promote/production Build is passing
2024-01-29 20:55:29 +01:00
aminecmi 3bc0d7cf95 Changelog for v124010191 [CI SKIP] 2024-01-19 21:16:23 +00:00
aminecmi 8f464d95fd fix: moving listeners.
continuous-integration/drone/push Build is passing
continuous-integration/drone/promote/production Build is passing
2024-01-19 21:32:43 +01:00
aminecmi 5ccd6a3368 chore: removed a useless log. 2024-01-19 21:32:43 +01:00
aminecmi cdbded246e Changelog for v124010032 [CI SKIP] 2024-01-03 22:16:35 +00:00
aminecmi 750c7758bd fix: Another date format thing.
continuous-integration/drone/push Build is passing
continuous-integration/drone/promote/production Build is passing
2024-01-03 23:07:57 +01:00
aminecmi 22f8b14ecd Changelog for v124010031 [CI SKIP] 2024-01-03 21:46:48 +00:00
aminecmi 6e27d6d4e6 fix: Checking if selfoss instance.
continuous-integration/drone/push Build is passing
continuous-integration/drone/promote/production Build is passing
2024-01-03 22:28:57 +01:00
aminecmi 14ff4dbd05 fix: handle three characters lenght hexcode colors. 2024-01-03 21:35:58 +01:00
aminecmi 390c2d0cf3 Changelog for v123113311 [CI SKIP] 2023-11-27 20:48:32 +00:00
aminecmi e58914ef58 chore: Source tracker url in the menu.
continuous-integration/drone/push Build is passing
continuous-integration/drone/promote/production Build is passing
2023-11-27 21:31:46 +01:00
aminecmi a03f08fca1 fix: Handle kodein proguard rules. 2023-11-27 21:31:26 +01:00
aminecmi 8e9b87f00c Changelog for v123102961 [CI SKIP] 2023-10-23 21:11:15 +00:00
aminecmi f765224a86 chore: domain changes.
continuous-integration/drone/push Build is passing
continuous-integration/drone/promote/production Build is passing
2023-10-21 23:41:17 +02:00
aminecmi 14d2219eb8 Changelog for v123102852 [CI SKIP] 2023-10-12 18:48:06 +00:00
26 changed files with 322 additions and 185 deletions
+4 -4
View File
@@ -75,7 +75,7 @@ steps:
- name: scpFiles
image: appleboy/drone-scp
settings:
host: amine-louveau.fr
host: amine-bouabdallaoui.fr
username: ubuntu
key:
from_secret: privateKey
@@ -86,7 +86,7 @@ steps:
- name: deploy
image: appleboy/drone-ssh
settings:
host: amine-louveau.fr
host: amine-bouabdallaoui.fr
user: ubuntu
key:
from_secret: privateKey
@@ -120,7 +120,7 @@ steps:
- ./gradlew :androidApp:assembleGithubConfigRelease
- echo "---------------------------------------------------------"
- echo "Get Key"
- wget https://amine-louveau.fr/key
- wget https://amine-bouabdallaoui.fr/key
- echo "---------------------------------------------------------"
- echo "Zipalign"
- $ANDROID_HOME/build-tools/31.0.0/zipalign -f -v 4 androidApp/build/outputs/apk/githubConfig/release/androidApp-githubConfig-release-unsigned.apk androidApp/build/outputs/apk/githubConfig/release/android-prod-released-ziped.apk
@@ -142,7 +142,7 @@ steps:
settings:
api_key:
from_secret: giteaAPI
base_url: https://gitea.amine-louveau.fr
base_url: https://gitea.amine-bouabdallaoui.fr
files: signed.apk
- name: notify
+1 -1
View File
@@ -10,7 +10,7 @@ Please read the guidelines before contributing, and follow them (or try to) when
There are many ways to contribute to this project, you could [translate the app](https://crowdin.com/project/readerforselfoss), report bugs, request missing features, suggest enhancements and changes to existing ones. You also can improve the README with useful tips that could help the other users.
You can fork the repository, and [help me solve some issues](https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/issues?q=is%3Aissue+is%3Aopen+label%3A%22Up+For+Grabs%22) or [develop new things](https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/issues)
You can fork the repository, and [help me solve some issues](https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderForSelfoss-multiplatform/issues?q=is%3Aissue+is%3Aopen+label%3A%22Up+For+Grabs%22) or [develop new things](https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderForSelfoss-multiplatform/issues)
### What I can't help you with.
+74
View File
@@ -1,3 +1,77 @@
**v124041081**
- chore: comment.
- fix: Last time fixing the parsing date hack before moving it to os version.
- Changelog for v124030731 [CI SKIP]
--------------------------------------------------------------------
**v124030731**
- fix: Basic auth and password can have non whitspace characters. Fixes 142.
- Changelog for v124020451 [CI SKIP]
--------------------------------------------------------------------
**v124020451**
- fix: Fixed handling of position in card adapter.
- Changelog for v124010301 [CI SKIP]
--------------------------------------------------------------------
**v124010301**
- fix: This may fix the oom errors.
- Changelog for v124010191 [CI SKIP]
--------------------------------------------------------------------
**v124010191**
- fix: moving listeners.
- chore: removed a useless log.
- Changelog for v124010032 [CI SKIP]
--------------------------------------------------------------------
**v124010032**
- fix: Another date format thing.
- Changelog for v124010031 [CI SKIP]
--------------------------------------------------------------------
**v124010031**
- fix: Checking if selfoss instance.
- fix: handle three characters lenght hexcode colors.
- Changelog for v123113311 [CI SKIP]
--------------------------------------------------------------------
**v123113311**
- chore: Source tracker url in the menu.
- fix: Handle kodein proguard rules.
- Changelog for v123102961 [CI SKIP]
--------------------------------------------------------------------
**v123102961**
- chore: domain changes.
- Changelog for v123102852 [CI SKIP]
--------------------------------------------------------------------
**v123102852**
- chore: lint cleaning.
- Changelog for v123102841 [CI SKIP]
--------------------------------------------------------------------
**v123102841**
- chore: cleaning ci steps and upgrading dependencies.
+6 -6
View File
@@ -1,4 +1,4 @@
# ReaderForSelfoss-multiplatform [![Build Status](https://build.amine-louveau.fr/api/badges/Louvorg/ReaderForSelfoss-multiplatform/status.svg)](https://build.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform)
# ReaderForSelfoss-multiplatform [![Build Status](https://build.amine-bouabdallaoui.fr/api/badges/Louvorg/ReaderForSelfoss-multiplatform/status.svg)](https://build.amine-bouabdallaoui.fr/Louvorg/ReaderForSelfoss-multiplatform)
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/readerforselfoss/localized.svg)](https://crowdin.com/project/readerforselfoss)
@@ -22,15 +22,15 @@ If you are a user, you can still create new issues. I'll fix them when I can.
1. **You'll have to have a Selfoss instance running.** You'll 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).
2. Check the [Contribution guide](https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderforSelfoss-multiplatform/src/branch/master/.github/CONTRIBUTING.md).
3. Build the project by following [these steps](https://gitea.amine-louveau.fr/Louvorg/ReaderforSelfoss-multiplatform/src/branch/master/.github/CONTRIBUTING.md#build-the-project) (you should have read them after the contribution guide)
3. Build the project by following [these steps](https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderforSelfoss-multiplatform/src/branch/master/.github/CONTRIBUTING.md#build-the-project) (you should have read them after the contribution guide)
## Useful links
- [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)
- [Check what changed](https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderforSelfoss-multiplatform/src/branch/master/CHANGELOG.md)
- [See what I'm doing](https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderforSelfoss-multiplatform/projects/1)
- [Create an issue, or request a new feature](https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderforSelfoss-multiplatform/issues)
- [Help translation the app](https://crowdin.com/project/readerforselfoss)
## Contributors (V1) (Alphabetical order) ❤️
+9
View File
@@ -86,3 +86,12 @@
-dontwarn io.mockk.**
-keep class io.mockk.** { *; }
# Kodein
-keep, allowobfuscation, allowoptimization class org.kodein.type.TypeReference
-keep, allowobfuscation, allowoptimization class org.kodein.type.JVMAbstractTypeToken$Companion$WrappingTest
-keep, allowobfuscation, allowoptimization class * extends org.kodein.type.TypeReference
-keep, allowobfuscation, allowoptimization class * extends org.kodein.type.JVMAbstractTypeToken$Companion$WrappingTest
@@ -1,6 +1,7 @@
package bou.amine.apps.readerforselfossv2.android
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
@@ -12,7 +13,11 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SearchView
import androidx.core.view.doOnNextLayout
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.*
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import androidx.work.Constraints
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.PeriodicWorkRequestBuilder
@@ -57,7 +62,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
private lateinit var recyclerViewScrollListener: RecyclerView.OnScrollListener
private lateinit var binding: ActivityHomeBinding
private var recyclerAdapter: RecyclerView.Adapter<*>? = null
private var recyclerAdapter: ItemsAdapter<out RecyclerView.ViewHolder>? = null
private var fromTabShortcut: Boolean = false
@@ -497,7 +502,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
}
binding.recyclerView.adapter = recyclerAdapter
} else {
(recyclerAdapter as ItemsAdapter<*>).updateAllItems(items)
recyclerAdapter!!.updateAllItems(items)
}
reloadBadges()
@@ -565,6 +570,11 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.issue_tracker -> {
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(AppSettingsService.trackerUrl))
startActivity(browserIntent)
return true
}
R.id.action_filter -> {
val filterSheetFragment = FilterSheetFragment()
filterSheetFragment.show(supportFragmentManager, FilterSheetFragment.TAG)
@@ -4,6 +4,7 @@ import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.annotation.SuppressLint
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.text.TextUtils
import android.view.Menu
@@ -139,20 +140,25 @@ class LoginActivity : AppCompatActivity(), DIAware {
repository.refreshLoginInformation(url, login, password)
CoroutineScope(Dispatchers.Main).launch {
repository.updateApiInformation()
try {
repository.updateApiInformation()
} catch (e: Exception) {
if (e.message?.startsWith("No transformation found") == true) {
Toast.makeText(
applicationContext,
R.string.application_selfoss_only,
Toast.LENGTH_LONG,
).show()
preferenceError()
showProgress(false)
}
}
val result = repository.login()
if (result) {
val (errorFetching, displaySelfossOnly) = repository.shouldBeSelfossInstance()
if (!errorFetching && !displaySelfossOnly) {
val errorFetching = repository.checkIfFetchFails()
if (!errorFetching) {
goToMain()
} else {
if (displaySelfossOnly) {
Toast.makeText(
applicationContext,
R.string.application_selfoss_only,
Toast.LENGTH_LONG,
).show()
}
preferenceError()
}
} else {
@@ -254,10 +260,17 @@ class LoginActivity : AppCompatActivity(), DIAware {
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.issue_tracker -> {
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(AppSettingsService.trackerUrl))
startActivity(browserIntent)
return true
}
R.id.about -> {
LibsBuilder()
.withAboutIconShown(true)
.withAboutVersionShown(true)
.withAboutSpecial2("Bug reports").withAboutSpecial2Description(AppSettingsService.trackerUrl)
.withAboutSpecial1("Project Page").withAboutSpecial1Description(AppSettingsService.sourceUrl)
.start(this)
true
}
@@ -132,7 +132,7 @@ class MyApp : MultiDexApplication(), DIAware {
}
httpSender {
uri =
"https://bugs.amine-louveau.fr/report" // best guess, you may need to adjust this
"https://bugs.amine-bouabdallaoui.fr/report" // best guess, you may need to adjust this
basicAuthLogin = "qMEscjj89Gwt6cPR"
basicAuthPassword = "Yo58QFlGzFaWlBzP"
httpMethod = HttpSender.Method.POST
@@ -1,7 +1,6 @@
package bou.amine.apps.readerforselfossv2.android.adapters
import android.app.Activity
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -9,11 +8,11 @@ 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.sendSilentlyWithAcraWithName
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.openInBrowserAsNewTask
import bou.amine.apps.readerforselfossv2.android.utils.openItemUrl
import bou.amine.apps.readerforselfossv2.android.utils.shareLink
import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.repository.Repository
@@ -31,10 +30,10 @@ import org.kodein.di.instance
class ItemCardAdapter(
override val app: Activity,
override var items: ArrayList<SelfossModel.Item>,
override val updateItems: (ArrayList<SelfossModel.Item>) -> Unit,
override val items: ArrayList<SelfossModel.Item>,
override val updateHomeItems: (ArrayList<SelfossModel.Item>) -> Unit,
) : ItemsAdapter<ItemCardAdapter.ViewHolder>() {
private val c: Context = app.baseContext
override lateinit var binding: CardItemBinding
private val imageMaxHeight: Int =
c.resources.getDimension(R.dimen.card_image_max_height).toInt()
@@ -46,10 +45,36 @@ class ItemCardAdapter(
parent: ViewGroup,
viewType: Int,
): ViewHolder {
val binding = CardItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
binding = CardItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding)
}
private fun handleClickListeners(holderBinding: CardItemBinding, position: Int) {
holderBinding.favButton.setOnClickListener {
val item = items[position]
if (item.starred) {
CoroutineScope(Dispatchers.IO).launch {
repository.unstarr(item)
}
binding.favButton.isSelected = false
} else {
CoroutineScope(Dispatchers.IO).launch {
repository.starr(item)
}
binding.favButton.isSelected = true
}
}
binding.shareBtn.setOnClickListener {
val item = items[position]
c.shareLink(item.getLinkDecoded(), item.title.getHtmlDecoded())
}
binding.browserBtn.setOnClickListener {
c.openInBrowserAsNewTask(items[position])
}
}
override fun onBindViewHolder(
holder: ViewHolder,
position: Int,
@@ -57,6 +82,9 @@ class ItemCardAdapter(
with(holder) {
val itm = items[position]
handleClickListeners(binding, position)
handleLinkOpening(binding, position)
binding.favButton.isSelected = itm.starred
if (appSettingsService.getPublicAccess()) {
binding.favButton.visibility = View.GONE
@@ -68,7 +96,12 @@ class ItemCardAdapter(
binding.title.setLinkTextColor(c.resources.getColor(R.color.colorAccent))
binding.sourceTitleAndDate.text = itm.sourceAuthorAndDate()
binding.sourceTitleAndDate.text = try {
itm.sourceAuthorAndDate()
} catch (e: Exception) {
e.sendSilentlyWithAcraWithName("ItemCardAdapter parse date")
itm.sourceAuthorOnly()
}
if (!appSettingsService.isFullHeightCardsEnabled()) {
binding.itemImage.maxHeight = imageMaxHeight
@@ -92,52 +125,5 @@ class ItemCardAdapter(
}
}
override fun getItemCount(): Int {
return items.size
}
inner class ViewHolder(val binding: CardItemBinding) : RecyclerView.ViewHolder(binding.root) {
init {
handleClickListeners()
handleLinkOpening()
}
private fun handleClickListeners() {
binding.favButton.setOnClickListener {
val item = items[bindingAdapterPosition]
if (item.starred) {
CoroutineScope(Dispatchers.IO).launch {
repository.unstarr(item)
}
binding.favButton.isSelected = false
} else {
CoroutineScope(Dispatchers.IO).launch {
repository.starr(item)
}
binding.favButton.isSelected = true
}
}
binding.shareBtn.setOnClickListener {
val item = items[bindingAdapterPosition]
c.shareLink(item.getLinkDecoded(), item.title.getHtmlDecoded())
}
binding.browserBtn.setOnClickListener {
c.openInBrowserAsNewTask(items[bindingAdapterPosition])
}
}
private fun handleLinkOpening() {
binding.root.setOnClickListener {
repository.setReaderItems(items)
c.openItemUrl(
bindingAdapterPosition,
items[bindingAdapterPosition].getLinkDecoded(),
appSettingsService.isArticleViewerEnabled(),
app,
)
}
}
}
inner class ViewHolder(val binding: CardItemBinding) : RecyclerView.ViewHolder(binding.root)
}
@@ -1,15 +1,14 @@
package bou.amine.apps.readerforselfossv2.android.adapters
import android.app.Activity
import android.content.Context
import android.view.LayoutInflater
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.sendSilentlyWithAcraWithName
import bou.amine.apps.readerforselfossv2.android.utils.LinkOnTouchListener
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularDrawable
import bou.amine.apps.readerforselfossv2.android.utils.openItemUrl
import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
@@ -22,10 +21,10 @@ import org.kodein.di.instance
class ItemListAdapter(
override val app: Activity,
override var items: ArrayList<SelfossModel.Item>,
override val updateItems: (ArrayList<SelfossModel.Item>) -> Unit,
override val items: ArrayList<SelfossModel.Item>,
override val updateHomeItems: (ArrayList<SelfossModel.Item>) -> Unit,
) : ItemsAdapter<ItemListAdapter.ViewHolder>() {
private val c: Context = app.baseContext
override lateinit var binding: ListItemBinding
override val di: DI by closestDI(app)
override val repository: Repository by instance()
@@ -35,7 +34,7 @@ class ItemListAdapter(
parent: ViewGroup,
viewType: Int,
): ViewHolder {
val binding = ListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
binding = ListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding)
}
@@ -46,13 +45,20 @@ class ItemListAdapter(
with(holder) {
val itm = items[position]
handleLinkOpening(binding, position)
binding.title.text = itm.title.getHtmlDecoded()
binding.title.setOnTouchListener(LinkOnTouchListener())
binding.title.setLinkTextColor(c.resources.getColor(R.color.colorAccent))
binding.sourceTitleAndDate.text = itm.sourceAuthorAndDate()
binding.sourceTitleAndDate.text = try {
itm.sourceAuthorAndDate()
} catch (e: Exception) {
e.sendSilentlyWithAcraWithName("ItemListAdapter parse date")
itm.sourceAuthorOnly()
}
if (itm.getThumbnail(repository.baseUrl).isEmpty()) {
if (itm.getIcon(repository.baseUrl).isEmpty()) {
@@ -66,23 +72,5 @@ class ItemListAdapter(
}
}
override fun getItemCount(): Int = items.size
inner class ViewHolder(val binding: ListItemBinding) : RecyclerView.ViewHolder(binding.root) {
init {
handleLinkOpening()
}
private fun handleLinkOpening() {
binding.root.setOnClickListener {
repository.setReaderItems(items)
c.openItemUrl(
bindingAdapterPosition,
items[bindingAdapterPosition].getLinkDecoded(),
appSettingsService.isArticleViewerEnabled(),
app,
)
}
}
}
inner class ViewHolder(val binding: ListItemBinding) : RecyclerView.ViewHolder(binding.root)
}
@@ -1,10 +1,13 @@
package bou.amine.apps.readerforselfossv2.android.adapters
import android.app.Activity
import android.content.Context
import android.graphics.Color
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding
import bou.amine.apps.readerforselfossv2.android.R
import bou.amine.apps.readerforselfossv2.android.utils.openItemUrl
import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
@@ -16,16 +19,20 @@ import kotlinx.coroutines.launch
import org.kodein.di.DIAware
abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapter<VH>(), DIAware {
abstract var items: ArrayList<SelfossModel.Item>
abstract val items: ArrayList<SelfossModel.Item>
abstract val repository: Repository
abstract val binding: ViewBinding
abstract val appSettingsService: AppSettingsService
abstract val app: Activity
abstract val updateItems: (ArrayList<SelfossModel.Item>) -> Unit
abstract val updateHomeItems: (ArrayList<SelfossModel.Item>) -> Unit
protected val c: Context get() = app.baseContext
fun updateAllItems(items: ArrayList<SelfossModel.Item>) {
this.items = items
this.items.clear()
this.items.addAll(items)
updateHomeItems(items)
notifyDataSetChanged()
updateItems(this.items)
}
private fun unmarkSnackbar(
@@ -70,6 +77,18 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
s.show()
}
protected fun handleLinkOpening(holderBinding: ViewBinding, position: Int) {
holderBinding.root.setOnClickListener {
repository.setReaderItems(items)
c.openItemUrl(
position,
items[position].getLinkDecoded(),
appSettingsService.isArticleViewerEnabled(),
app,
)
}
}
fun handleItemAtIndex(position: Int) {
if (items[position].unread) {
readItemAtIndex(items[position], position)
@@ -89,7 +108,8 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
if (repository.displayedItems == ItemType.UNREAD) {
items.remove(item)
notifyItemRemoved(position)
updateItems(items)
notifyItemRangeChanged(position, itemCount)
updateHomeItems(items)
} else {
notifyItemChanged(position)
}
@@ -118,13 +138,15 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
) {
items.add(position, item)
notifyItemInserted(position)
updateItems(items)
updateHomeItems(items)
}
fun addItemsAtEnd(newItems: List<SelfossModel.Item>) {
val oldSize = items.size
items.addAll(newItems)
notifyItemRangeInserted(oldSize, newItems.size)
updateItems(items)
updateHomeItems(items)
}
override fun getItemCount(): Int = items.size
}
@@ -50,6 +50,33 @@ class SourcesListAdapter(
) {
val itm = items[position]
val deleteBtn: Button = holder.mView.findViewById(R.id.deleteBtn)
deleteBtn.setOnClickListener {
val (id, title) = items[position]
CoroutineScope(Dispatchers.IO).launch {
val successfullyDeletedSource = repository.deleteSource(id, title)
if (successfullyDeletedSource) {
items.removeAt(position)
notifyItemRemoved(position)
notifyItemRangeChanged(position, itemCount)
} else {
Toast.makeText(
app,
R.string.can_delete_source,
Toast.LENGTH_SHORT,
).show()
}
}
}
holder.mView.setOnClickListener {
val source = items[position]
repository.setSelectedSource(source)
app.startActivity(Intent(app, UpsertSourceActivity::class.java))
}
if (itm.getIcon(repository.baseUrl).isEmpty()) {
binding.itemImage.setBackgroundAndText(itm.title.getHtmlDecoded())
} else {
@@ -72,38 +99,5 @@ class SourcesListAdapter(
override fun getItemCount(): Int = items.size
inner class ViewHolder(private val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) {
init {
handleClickListeners()
}
private fun handleClickListeners() {
val deleteBtn: Button = mView.findViewById(R.id.deleteBtn)
deleteBtn.setOnClickListener {
val (id, title) = items[bindingAdapterPosition]
CoroutineScope(Dispatchers.IO).launch {
val successfullyDeletedSource = repository.deleteSource(id, title)
if (successfullyDeletedSource) {
items.removeAt(bindingAdapterPosition)
notifyItemRemoved(bindingAdapterPosition)
notifyItemRangeChanged(bindingAdapterPosition, itemCount)
} else {
Toast.makeText(
app,
R.string.can_delete_source,
Toast.LENGTH_SHORT,
).show()
}
}
}
mView.setOnClickListener {
val source = items[bindingAdapterPosition]
repository.setSelectedSource(source)
app.startActivity(Intent(app, UpsertSourceActivity::class.java))
}
}
}
inner class ViewHolder(val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView)
}
@@ -52,7 +52,6 @@ import org.kodein.di.DIAware
import org.kodein.di.android.x.closestDI
import org.kodein.di.instance
import java.net.MalformedURLException
import java.net.SocketTimeoutException
import java.net.URL
import java.util.*
import java.util.concurrent.ExecutionException
@@ -103,7 +102,12 @@ class ArticleFragment : Fragment(), DIAware {
contentText = item.content
contentTitle = item.title.getHtmlDecoded()
contentImage = item.getThumbnail(repository.baseUrl)
contentSource = item.sourceAuthorAndDate()
contentSource = try {
item.sourceAuthorAndDate()
} catch (e: Exception) {
e.sendSilentlyWithAcraWithName("Article Fragment parse date")
item.sourceAuthorOnly()
}
allImages = item.getImages()
fontSize = appSettingsService.getFontSize()
@@ -264,10 +268,7 @@ class ArticleFragment : Fragment(), DIAware {
} else {
openInBrowserAfterFailing()
}
} catch (e: SocketTimeoutException) {
openInBrowserAfterFailing()
} catch (e: Exception) {
e.sendSilentlyWithAcraWithName("getContentFromMercury > $url")
openInBrowserAfterFailing()
}
}
@@ -17,6 +17,7 @@ import bou.amine.apps.readerforselfossv2.android.R
import bou.amine.apps.readerforselfossv2.android.databinding.FilterFragmentBinding
import bou.amine.apps.readerforselfossv2.android.sendSilentlyWithAcraWithName
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.utils.getColorHexCode
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
import bou.amine.apps.readerforselfossv2.utils.getIcon
import com.bumptech.glide.Glide
@@ -147,9 +148,9 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
val gd = GradientDrawable()
val gdColor =
try {
Color.parseColor(tag.color)
Color.parseColor(tag.getColorHexCode())
} catch (e: IllegalArgumentException) {
e.sendSilentlyWithAcraWithName("color issue " + tag.color)
e.sendSilentlyWithAcraWithName("color issue " + tag.color + " / " + tag.getColorHexCode())
resources.getColor(R.color.colorPrimary)
}
gd.setColor(gdColor)
@@ -36,6 +36,12 @@
android:orderInCategory="101"
android:title="@string/menu_home_refresh" />
<item
android:id="@+id/issue_tracker"
app:showAsAction="never"
android:orderInCategory="103"
android:title="@string/issue_tracker_link" />
<item android:id="@+id/action_disconnect"
android:title="@string/action_disconnect"
android:orderInCategory="104"
@@ -3,6 +3,13 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/issue_tracker"
app:showAsAction="never"
android:orderInCategory="101"
android:title="@string/issue_tracker_link" />
<item android:id="@+id/about"
android:title="@string/action_about"
android:orderInCategory="102"
+15 -4
View File
@@ -8,10 +8,12 @@ import kotlinx.datetime.toInstant
import org.junit.Test
class DatesTest {
private val newVersionDateVariant = "2022-12-24T17:00:08+00"
private val newVersionDate = "2013-04-07T13:43:00+01:00"
private val oldVersionDate = "2013-05-07 13:46:00"
private val oldVersionDateVariant = "2021-03-21 10:32:00.000000"
private val newVersionDateVariant = "2022-12-24T17:00:08+00"
private val newVersionDate = "2013-04-07T13:43:00+01:00"
private val newVersionDate2 = "2013-04-07T13:43:00-01:00"
private val oldVersionDate = "2013-05-07 13:46:00"
private val oldVersionDateVariant = "2021-03-21 10:32:00.000000"
@Test
fun new_version_date_should_be_parsed() {
@@ -22,6 +24,15 @@ class DatesTest {
assertEquals(expected, date)
}
@Test
fun new_version_date2_should_be_parsed() {
val date = DateUtils.parseDate(newVersionDate2)
val expected =
LocalDateTime(2013, 4, 7, 13, 43, 0, 0).toInstant(TimeZone.currentSystemDefault())
.toEpochMilliseconds()
assertEquals(expected, date)
}
@Test
fun old_version_date_should_be_parsed() {
-1
View File
@@ -17,7 +17,6 @@ plugins {
allprojects {
repositories {
// maven { url = uri("https://nexus.amine-louveau.fr/repository/maven-public/")}
google()
mavenCentral()
maven { url = uri("https://www.jitpack.io") }
+2 -2
View File
@@ -68,9 +68,9 @@ redirect_from: "/ReaderforSelfoss-multiplatform/"
<div id="links">
<a class="github-button" href="https://gitea.amine-louveau.fr/Louvorg/readerforselfoss-multiplatform" data-size="large" aria-label="Star aminecmi/readerforselfoss-multiplatform on GitHub">Star</a>
<a class="github-button" href="https://gitea.amine-bouabdallaoui.fr/Louvorg/readerforselfoss-multiplatform" data-size="large" aria-label="Star aminecmi/readerforselfoss-multiplatform on GitHub">Star</a>
</div>
<meta itemprop="url" content="https://gitea.amine-louveau.fr/Louvorg/readerforselfoss-multiplatform">
<meta itemprop="url" content="https://gitea.amine-bouabdallaoui.fr/Louvorg/readerforselfoss-multiplatform">
<meta itemprop="applicationCategory" content="News & Magazines">
</div>
</body>
-2
View File
@@ -1,6 +1,5 @@
pluginManagement {
repositories {
// maven { url = uri("https://nexus.amine-louveau.fr/repository/maven-public/")}
google()
gradlePluginPortal()
mavenCentral()
@@ -9,7 +8,6 @@ pluginManagement {
dependencyResolutionManagement {
repositories {
// maven { url = uri("https://nexus.amine-louveau.fr/repository/maven-public/")}
google()
mavenCentral()
}
@@ -1,6 +1,7 @@
package bou.amine.apps.readerforselfossv2.utils
import android.text.format.DateUtils
import io.github.aakira.napier.Napier
import kotlinx.datetime.*
actual class DateUtils {
@@ -10,18 +11,21 @@ actual class DateUtils {
private val oldVersionFormat = "\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}(.()\\d*)?".toRegex()
// yyyy-MM-dd'T'HH:mm:ss[.SSS]XXX (RFC3339)
private val newVersionFormat = "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\+\\d{2}(:\\d{2})?".toRegex()
private val newVersionFormat = "(\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2})[+-](\\d{2}(:\\d{2})?)?".toRegex()
// We may need to consider moving the formatting to platform specific code, even if the tests are doubled
// For now, we handle this in a hacky way, because kotlin only accepts iso formats
// TODO: do not fix any more issues here. Move everything to plateform specific code.
actual fun parseDate(dateString: String): Long {
var isoDateString: String =
if (dateString.matches(oldVersionFormat)) {
dateString.replace(" ", "T")
} else if (dateString.matches(newVersionFormat)) {
dateString.split("+")[0]
} else {
throw Exception("Unrecognized format for $dateString")
try {
if (dateString.matches(oldVersionFormat)) {
dateString.replace(" ", "T")
} else if (dateString.matches(newVersionFormat)) {
newVersionFormat.find(dateString)?.groups?.get(1)?.value ?: throw Exception("Couldn't parse $dateString")
} else {
throw Exception("Unrecognized format for $dateString")
}
} catch (e: Exception) {
throw Exception("parseDate failed for $dateString", e)
}
return LocalDateTime.parse(isoDateString).toInstant(TimeZone.currentSystemDefault()).toEpochMilliseconds()
@@ -146,6 +146,14 @@ class SelfossModel {
return txt
}
fun sourceAuthorOnly(): String {
var txt = this.sourcetitle.getHtmlDecoded()
if (!this.author.isNullOrBlank()) {
txt += " (by ${this.author}) "
}
return txt
}
fun toggleStar(): Item {
this.starred = !this.starred
return this
@@ -8,7 +8,6 @@ import bou.amine.apps.readerforselfossv2.rest.SelfossApi
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import bou.amine.apps.readerforselfossv2.utils.*
import io.github.aakira.napier.Napier
import io.ktor.client.call.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
@@ -423,28 +422,25 @@ class Repository(
val response = api.login()
result = response.isSuccess == true
} catch (cause: Throwable) {
Napier.e(cause.stackTraceToString(), tag = "RepositoryImpl.login")
Napier.e("login failed", cause, tag = "RepositoryImpl.login")
}
}
return result
}
suspend fun shouldBeSelfossInstance(): Pair<Boolean, Boolean> {
suspend fun checkIfFetchFails(): Boolean {
var fetchFailed = true
var showSelfossOnlyModal = false
if (isNetworkAvailable()) {
try {
// Trying to fetch one item, and check someone is trying to use the app with
// a random rss feed, that would throw a NoTransformationFoundException
fetchFailed = !api.getItemsWithoutCatch().success
} catch (e: NoTransformationFoundException) {
showSelfossOnlyModal = true
} catch (e: Throwable) {
Napier.e(e.stackTraceToString(), tag = "RepositoryImpl.shouldBeSelfossInstance")
Napier.e("checkIfFetchFails failed", e, tag = "RepositoryImpl.shouldBeSelfossInstance")
}
}
return Pair(fetchFailed, showSelfossOnlyModal)
return fetchFailed
}
suspend fun logout() {
@@ -455,7 +451,7 @@ class Repository(
Napier.e("Couldn't logout.", tag = "RepositoryImpl.logout")
}
} catch (cause: Throwable) {
Napier.e(cause.stackTraceToString(), tag = "RepositoryImpl.logout")
Napier.e("logout failed", cause, tag = "RepositoryImpl.logout")
}
appSettingsService.clearAll()
} else {
@@ -40,7 +40,7 @@ class MercuryApi() {
suspend fun query(url: String): StatusAndData<MercuryModel.ParsedContent> =
bodyOrFailure(
client.get("https://amine-louveau.fr/parser.php") {
client.get("https://amine-bouabdallaoui.fr/parser.php") {
parameter("link", url)
},
)
@@ -441,7 +441,7 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
login: String,
password: String,
) {
val regex = """\/\/(\D+):(\D+)@""".toRegex()
val regex = """\/\/(\S+):(\S+)@""".toRegex()
val matchResult = regex.find(url)
if (matchResult != null) {
val (basicLogin, basicPassword) = matchResult.destructured
@@ -480,9 +480,9 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
companion object {
const val translationUrl = "https://crwd.in/readerforselfoss"
const val sourceUrl = "https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform"
const val sourceUrl = "https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderForSelfoss-multiplatform"
const val trackerUrl = "https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/issues"
const val trackerUrl = "https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderForSelfoss-multiplatform/issues"
const val syncChannelId = "sync-channel-id"
@@ -73,3 +73,13 @@ fun SelfossModel.Item.toEntity(): ITEM =
this.tags.joinToString(","),
this.author,
)
fun SelfossModel.Tag.getColorHexCode(): String =
if (this.color.length == 4) { // #000
val char1 = this.color.get(1)
val char2 = this.color.get(2)
val char3 = this.color.get(3)
"#$char1$char1$char2$char2$char3$char3"
} else {
this.color
}