Compare commits
	
		
			18 Commits
		
	
	
		
			v123113311
			...
			61e0087894
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					61e0087894 | ||
| 
						 | 
					1ec05d9913 | ||
| 
						 | 
					859bd91bbb | ||
| 
						 | 
					204b736c53 | ||
| 
						 | 
					f24609c143 | ||
| 
						 | 
					b94d7dc537 | ||
| 
						 | 
					41910cc4cd | ||
| 
						 | 
					db166ca9d4 | ||
| 
						 | 
					db0d5a4a85 | ||
| 
						 | 
					3bc0d7cf95 | ||
| 
						 | 
					8f464d95fd | ||
| 
						 | 
					5ccd6a3368 | ||
| 
						 | 
					cdbded246e | ||
| 
						 | 
					750c7758bd | ||
| 
						 | 
					22f8b14ecd | ||
| 
						 | 
					6e27d6d4e6 | ||
| 
						 | 
					14ff4dbd05 | ||
| 
						 | 
					390c2d0cf3 | 
							
								
								
									
										60
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										60
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -1,3 +1,63 @@
 | 
			
		||||
**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.
 | 
			
		||||
 
 | 
			
		||||
@@ -140,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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@ 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
 | 
			
		||||
@@ -34,6 +35,7 @@ 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()
 | 
			
		||||
@@ -46,16 +48,57 @@ 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(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[position]
 | 
			
		||||
            val itm = items[holder.bindingAdapterPosition]
 | 
			
		||||
 | 
			
		||||
            handleClickListeners(holder.bindingAdapterPosition)
 | 
			
		||||
            handleLinkOpening(holder.bindingAdapterPosition)
 | 
			
		||||
 | 
			
		||||
            binding.favButton.isSelected = itm.starred
 | 
			
		||||
            if (appSettingsService.getPublicAccess()) {
 | 
			
		||||
@@ -68,7 +111,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
 | 
			
		||||
@@ -96,48 +144,5 @@ class ItemCardAdapter(
 | 
			
		||||
        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)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@ 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
 | 
			
		||||
@@ -25,6 +26,7 @@ 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)
 | 
			
		||||
@@ -35,7 +37,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)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -44,7 +46,17 @@ class ItemListAdapter(
 | 
			
		||||
        position: Int,
 | 
			
		||||
    ) {
 | 
			
		||||
        with(holder) {
 | 
			
		||||
            val itm = items[position]
 | 
			
		||||
            val itm = items[holder.bindingAdapterPosition]
 | 
			
		||||
 | 
			
		||||
            binding.root.setOnClickListener {
 | 
			
		||||
                repository.setReaderItems(items)
 | 
			
		||||
                c.openItemUrl(
 | 
			
		||||
                    holder.bindingAdapterPosition,
 | 
			
		||||
                    items[holder.bindingAdapterPosition].getLinkDecoded(),
 | 
			
		||||
                    appSettingsService.isArticleViewerEnabled(),
 | 
			
		||||
                    app,
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            binding.title.text = itm.title.getHtmlDecoded()
 | 
			
		||||
 | 
			
		||||
@@ -52,7 +64,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()) {
 | 
			
		||||
@@ -68,21 +85,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)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -89,6 +89,7 @@ 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,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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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,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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
    }
 | 
			
		||||
		Reference in New Issue
	
	Block a user