Compare commits
11 Commits
v124010301
...
v124113251
Author | SHA1 | Date | |
---|---|---|---|
236e1cca90 | |||
3a33cb4510 | |||
0bf9ca9a49 | |||
61e0087894 | |||
1ec05d9913 | |||
859bd91bbb | |||
204b736c53 | |||
f24609c143 | |||
b94d7dc537 | |||
41910cc4cd | |||
db166ca9d4 |
29
CHANGELOG.md
29
CHANGELOG.md
@ -1,3 +1,32 @@
|
||||
**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.
|
||||
|
@ -13,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
|
||||
@ -58,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
|
||||
|
||||
@ -498,7 +502,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
}
|
||||
binding.recyclerView.adapter = recyclerAdapter
|
||||
} else {
|
||||
(recyclerAdapter as ItemsAdapter<*>).updateAllItems(items)
|
||||
recyclerAdapter!!.updateAllItems(items)
|
||||
}
|
||||
|
||||
reloadBadges()
|
||||
|
@ -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,11 +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 lateinit var binding: CardItemBinding
|
||||
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()
|
||||
|
||||
@ -51,8 +49,8 @@ class ItemCardAdapter(
|
||||
return ViewHolder(binding)
|
||||
}
|
||||
|
||||
private fun handleClickListeners(position: Int) {
|
||||
binding.favButton.setOnClickListener {
|
||||
private fun handleClickListeners(holderBinding: CardItemBinding, position: Int) {
|
||||
holderBinding.favButton.setOnClickListener {
|
||||
val item = items[position]
|
||||
if (item.starred) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
@ -77,18 +75,6 @@ class ItemCardAdapter(
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleLinkOpening(position: Int) {
|
||||
binding.root.setOnClickListener {
|
||||
repository.setReaderItems(items)
|
||||
c.openItemUrl(
|
||||
position,
|
||||
items[position].getLinkDecoded(),
|
||||
appSettingsService.isArticleViewerEnabled(),
|
||||
app,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(
|
||||
holder: ViewHolder,
|
||||
position: Int,
|
||||
@ -96,8 +82,8 @@ class ItemCardAdapter(
|
||||
with(holder) {
|
||||
val itm = items[position]
|
||||
|
||||
handleClickListeners(position)
|
||||
handleLinkOpening(position)
|
||||
handleClickListeners(binding, position)
|
||||
handleLinkOpening(binding, position)
|
||||
|
||||
binding.favButton.isSelected = itm.starred
|
||||
if (appSettingsService.getPublicAccess()) {
|
||||
@ -110,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
|
||||
@ -134,9 +125,5 @@ class ItemCardAdapter(
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return items.size
|
||||
}
|
||||
|
||||
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,11 +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 lateinit var binding: ListItemBinding
|
||||
private val c: Context = app.baseContext
|
||||
override lateinit var binding: ListItemBinding
|
||||
|
||||
override val di: DI by closestDI(app)
|
||||
override val repository: Repository by instance()
|
||||
@ -47,15 +45,7 @@ class ItemListAdapter(
|
||||
with(holder) {
|
||||
val itm = items[position]
|
||||
|
||||
binding.root.setOnClickListener {
|
||||
repository.setReaderItems(items)
|
||||
c.openItemUrl(
|
||||
bindingAdapterPosition,
|
||||
items[bindingAdapterPosition].getLinkDecoded(),
|
||||
appSettingsService.isArticleViewerEnabled(),
|
||||
app,
|
||||
)
|
||||
}
|
||||
handleLinkOpening(binding, position)
|
||||
|
||||
binding.title.text = itm.title.getHtmlDecoded()
|
||||
|
||||
@ -63,7 +53,12 @@ class ItemListAdapter(
|
||||
|
||||
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()) {
|
||||
@ -77,7 +72,5 @@ class ItemListAdapter(
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = items.size
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -102,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()
|
||||
|
@ -8,12 +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"
|
||||
|
||||
private val bugVersionDate = "2023-12-19T10:30:53-05:00"
|
||||
|
||||
@Test
|
||||
fun new_version_date_should_be_parsed() {
|
||||
@ -24,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() {
|
||||
@ -54,14 +63,4 @@ class DatesTest {
|
||||
|
||||
assertEquals(expected, date)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun bug_version_variant_date_should_be_parsed() {
|
||||
val date = DateUtils.parseDate(bugVersionDate)
|
||||
val expected =
|
||||
LocalDateTime(1991, 3, 18, 3, 0, 0, 0).toInstant(TimeZone.currentSystemDefault())
|
||||
.toEpochMilliseconds()
|
||||
|
||||
assertEquals(expected, date)
|
||||
}
|
||||
}
|
||||
|
@ -11,23 +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 =
|
||||
try {
|
||||
if (dateString.matches(oldVersionFormat)) {
|
||||
dateString.replace(" ", "T")
|
||||
} else if (dateString.matches(newVersionFormat)) {
|
||||
dateString.split("+")[0]
|
||||
newVersionFormat.find(dateString)?.groups?.get(1)?.value ?: throw Exception("Couldn't parse $dateString")
|
||||
} else {
|
||||
throw Exception("Unrecognized format for $dateString")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Napier.e("parseDate failed", e, tag = "DateUtils.parseDate")
|
||||
"1991-03-18T03:00:00"
|
||||
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
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user