Compare commits
No commits in common. "master" and "v124010032" have entirely different histories.
master
...
v124010032
44
CHANGELOG.md
44
CHANGELOG.md
@ -1,47 +1,3 @@
|
||||
**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.
|
||||
|
@ -9,7 +9,6 @@ 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
|
||||
@ -35,7 +34,6 @@ class ItemCardAdapter(
|
||||
override var items: ArrayList<SelfossModel.Item>,
|
||||
override val updateItems: (ArrayList<SelfossModel.Item>) -> Unit,
|
||||
) : ItemsAdapter<ItemCardAdapter.ViewHolder>() {
|
||||
private lateinit var binding: CardItemBinding
|
||||
private val c: Context = app.baseContext
|
||||
private val imageMaxHeight: Int =
|
||||
c.resources.getDimension(R.dimen.card_image_max_height).toInt()
|
||||
@ -48,57 +46,16 @@ class ItemCardAdapter(
|
||||
parent: ViewGroup,
|
||||
viewType: Int,
|
||||
): ViewHolder {
|
||||
binding = CardItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
val binding = CardItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
return ViewHolder(binding)
|
||||
}
|
||||
|
||||
private fun handleClickListeners(position: Int) {
|
||||
binding.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])
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
) {
|
||||
with(holder) {
|
||||
val itm = items[holder.bindingAdapterPosition]
|
||||
|
||||
handleClickListeners(holder.bindingAdapterPosition)
|
||||
handleLinkOpening(holder.bindingAdapterPosition)
|
||||
val itm = items[position]
|
||||
|
||||
binding.favButton.isSelected = itm.starred
|
||||
if (appSettingsService.getPublicAccess()) {
|
||||
@ -111,12 +68,7 @@ class ItemCardAdapter(
|
||||
|
||||
binding.title.setLinkTextColor(c.resources.getColor(R.color.colorAccent))
|
||||
|
||||
binding.sourceTitleAndDate.text = try {
|
||||
itm.sourceAuthorAndDate()
|
||||
} catch (e: Exception) {
|
||||
e.sendSilentlyWithAcraWithName("ItemCardAdapter parse date")
|
||||
itm.sourceAuthorOnly()
|
||||
}
|
||||
binding.sourceTitleAndDate.text = itm.sourceAuthorAndDate()
|
||||
|
||||
if (!appSettingsService.isFullHeightCardsEnabled()) {
|
||||
binding.itemImage.maxHeight = imageMaxHeight
|
||||
@ -144,5 +96,48 @@ class ItemCardAdapter(
|
||||
return items.size
|
||||
}
|
||||
|
||||
inner class ViewHolder(val binding: CardItemBinding) : RecyclerView.ViewHolder(binding.root)
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ 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
|
||||
@ -26,7 +25,6 @@ class ItemListAdapter(
|
||||
override var items: ArrayList<SelfossModel.Item>,
|
||||
override val updateItems: (ArrayList<SelfossModel.Item>) -> Unit,
|
||||
) : ItemsAdapter<ItemListAdapter.ViewHolder>() {
|
||||
private lateinit var binding: ListItemBinding
|
||||
private val c: Context = app.baseContext
|
||||
|
||||
override val di: DI by closestDI(app)
|
||||
@ -37,7 +35,7 @@ class ItemListAdapter(
|
||||
parent: ViewGroup,
|
||||
viewType: Int,
|
||||
): ViewHolder {
|
||||
binding = ListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
val binding = ListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
return ViewHolder(binding)
|
||||
}
|
||||
|
||||
@ -46,17 +44,7 @@ class ItemListAdapter(
|
||||
position: Int,
|
||||
) {
|
||||
with(holder) {
|
||||
val itm = items[holder.bindingAdapterPosition]
|
||||
|
||||
binding.root.setOnClickListener {
|
||||
repository.setReaderItems(items)
|
||||
c.openItemUrl(
|
||||
holder.bindingAdapterPosition,
|
||||
items[holder.bindingAdapterPosition].getLinkDecoded(),
|
||||
appSettingsService.isArticleViewerEnabled(),
|
||||
app,
|
||||
)
|
||||
}
|
||||
val itm = items[position]
|
||||
|
||||
binding.title.text = itm.title.getHtmlDecoded()
|
||||
|
||||
@ -64,12 +52,7 @@ class ItemListAdapter(
|
||||
|
||||
binding.title.setLinkTextColor(c.resources.getColor(R.color.colorAccent))
|
||||
|
||||
binding.sourceTitleAndDate.text = try {
|
||||
itm.sourceAuthorAndDate()
|
||||
} catch (e: Exception) {
|
||||
e.sendSilentlyWithAcraWithName("ItemListAdapter parse date")
|
||||
itm.sourceAuthorOnly()
|
||||
}
|
||||
binding.sourceTitleAndDate.text = itm.sourceAuthorAndDate()
|
||||
|
||||
if (itm.getThumbnail(repository.baseUrl).isEmpty()) {
|
||||
if (itm.getIcon(repository.baseUrl).isEmpty()) {
|
||||
@ -85,5 +68,21 @@ class ItemListAdapter(
|
||||
|
||||
override fun getItemCount(): Int = items.size
|
||||
|
||||
inner class ViewHolder(val binding: ListItemBinding) : RecyclerView.ViewHolder(binding.root)
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -89,7 +89,6 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
|
||||
if (repository.displayedItems == ItemType.UNREAD) {
|
||||
items.remove(item)
|
||||
notifyItemRemoved(position)
|
||||
notifyItemRangeChanged(position, itemCount)
|
||||
updateItems(items)
|
||||
} else {
|
||||
notifyItemChanged(position)
|
||||
|
@ -50,33 +50,6 @@ 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 {
|
||||
@ -99,5 +72,38 @@ class SourcesListAdapter(
|
||||
|
||||
override fun getItemCount(): Int = items.size
|
||||
|
||||
inner class ViewHolder(val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView)
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -52,6 +52,7 @@ 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
|
||||
@ -102,12 +103,7 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
contentText = item.content
|
||||
contentTitle = item.title.getHtmlDecoded()
|
||||
contentImage = item.getThumbnail(repository.baseUrl)
|
||||
contentSource = try {
|
||||
item.sourceAuthorAndDate()
|
||||
} catch (e: Exception) {
|
||||
e.sendSilentlyWithAcraWithName("Article Fragment parse date")
|
||||
item.sourceAuthorOnly()
|
||||
}
|
||||
contentSource = item.sourceAuthorAndDate()
|
||||
allImages = item.getImages()
|
||||
|
||||
fontSize = appSettingsService.getFontSize()
|
||||
@ -268,7 +264,10 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
} else {
|
||||
openInBrowserAfterFailing()
|
||||
}
|
||||
} catch (e: SocketTimeoutException) {
|
||||
openInBrowserAfterFailing()
|
||||
} catch (e: Exception) {
|
||||
e.sendSilentlyWithAcraWithName("getContentFromMercury > $url")
|
||||
openInBrowserAfterFailing()
|
||||
}
|
||||
}
|
||||
|
@ -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 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 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 bugVersionDate = "2023-12-19T10:30:53-05:00"
|
||||
|
||||
@Test
|
||||
fun new_version_date_should_be_parsed() {
|
||||
@ -24,15 +24,6 @@ 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() {
|
||||
@ -63,4 +54,14 @@ 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,21 +11,23 @@ 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()
|
||||
|
||||
// TODO: do not fix any more issues here. Move everything to plateform specific code.
|
||||
// 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
|
||||
actual fun parseDate(dateString: String): Long {
|
||||
var isoDateString: String =
|
||||
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")
|
||||
dateString.split("+")[0]
|
||||
} else {
|
||||
throw Exception("Unrecognized format for $dateString")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
throw Exception("parseDate failed for $dateString", e)
|
||||
Napier.e(e.stackTraceToString(), tag = "DateUtils.parseDate")
|
||||
"1991-03-18T03:00:00"
|
||||
}
|
||||
|
||||
return LocalDateTime.parse(isoDateString).toInstant(TimeZone.currentSystemDefault()).toEpochMilliseconds()
|
||||
|
@ -146,14 +146,6 @@ 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
|
||||
|
@ -422,7 +422,7 @@ class Repository(
|
||||
val response = api.login()
|
||||
result = response.isSuccess == true
|
||||
} catch (cause: Throwable) {
|
||||
Napier.e("login failed", cause, tag = "RepositoryImpl.login")
|
||||
Napier.e(cause.stackTraceToString(), tag = "RepositoryImpl.login")
|
||||
}
|
||||
}
|
||||
return result
|
||||
@ -436,7 +436,7 @@ class Repository(
|
||||
// a random rss feed, that would throw a NoTransformationFoundException
|
||||
fetchFailed = !api.getItemsWithoutCatch().success
|
||||
} catch (e: Throwable) {
|
||||
Napier.e("checkIfFetchFails failed", e, tag = "RepositoryImpl.shouldBeSelfossInstance")
|
||||
Napier.e(e.stackTraceToString(), tag = "RepositoryImpl.shouldBeSelfossInstance")
|
||||
}
|
||||
}
|
||||
|
||||
@ -451,7 +451,7 @@ class Repository(
|
||||
Napier.e("Couldn't logout.", tag = "RepositoryImpl.logout")
|
||||
}
|
||||
} catch (cause: Throwable) {
|
||||
Napier.e("logout failed", cause, tag = "RepositoryImpl.logout")
|
||||
Napier.e(cause.stackTraceToString(), tag = "RepositoryImpl.logout")
|
||||
}
|
||||
appSettingsService.clearAll()
|
||||
} else {
|
||||
|
@ -441,7 +441,7 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
|
||||
login: String,
|
||||
password: String,
|
||||
) {
|
||||
val regex = """\/\/(\S+):(\S+)@""".toRegex()
|
||||
val regex = """\/\/(\D+):(\D+)@""".toRegex()
|
||||
val matchResult = regex.find(url)
|
||||
if (matchResult != null) {
|
||||
val (basicLogin, basicPassword) = matchResult.destructured
|
||||
|
Loading…
Reference in New Issue
Block a user