Compare commits
	
		
			15 Commits
		
	
	
		
			2c558fe6fd
			...
			v123030851
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 8d7d3174aa | |||
| 
						 | 
					00eb3333fe | ||
| 
						 | 
					629ca01d99 | ||
| 
						 | 
					c2d8681ce8 | ||
| 
						 | 
					08f79cb148 | ||
| e21906e70d | |||
| 
						 | 
					9d2cc32bc9 | ||
| 
						 | 
					d9d057c8dc | ||
| 
						 | 
					1f3fa0c4a6 | ||
| 
						 | 
					dea3def385 | ||
| 
						 | 
					f72ef2f5d4 | ||
| 
						 | 
					f28cb759df | ||
| 
						 | 
					b9d69c3e64 | ||
| 
						 | 
					c2a1c9eaac | ||
| 
						 | 
					bf37209a15 | 
							
								
								
									
										21
									
								
								.drone.yml
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								.drone.yml
									
									
									
									
									
								
							@@ -137,6 +137,27 @@ steps:
 | 
				
			|||||||
        from_secret: giteaAPI
 | 
					        from_secret: giteaAPI
 | 
				
			||||||
      base_url: https://gitea.amine-louveau.fr
 | 
					      base_url: https://gitea.amine-louveau.fr
 | 
				
			||||||
      files: signed.apk
 | 
					      files: signed.apk
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - name: notify
 | 
				
			||||||
 | 
					    image: drillster/drone-email
 | 
				
			||||||
 | 
					    failure: ignore
 | 
				
			||||||
 | 
					    settings:
 | 
				
			||||||
 | 
					      host:
 | 
				
			||||||
 | 
					        from_secret: smtpHOST
 | 
				
			||||||
 | 
					      port:
 | 
				
			||||||
 | 
					        from_secret: smtpPORT
 | 
				
			||||||
 | 
					      username:
 | 
				
			||||||
 | 
					        from_secret: smtpUSERNAME
 | 
				
			||||||
 | 
					      password:
 | 
				
			||||||
 | 
					        from_secret: smtpPASSWORD
 | 
				
			||||||
 | 
					      from:
 | 
				
			||||||
 | 
					        from_secret: smtpFROM
 | 
				
			||||||
 | 
					      subject: Mapping file
 | 
				
			||||||
 | 
					      recipients:
 | 
				
			||||||
 | 
					        from_secret: smtpTO
 | 
				
			||||||
 | 
					      recipients_only: true
 | 
				
			||||||
 | 
					      skip_verify: true
 | 
				
			||||||
 | 
					      attachment: androidApp/build/outputs/mapping/githubConfigRelease/mapping.txt
 | 
				
			||||||
trigger:
 | 
					trigger:
 | 
				
			||||||
  event:
 | 
					  event:
 | 
				
			||||||
    - tag
 | 
					    - tag
 | 
				
			||||||
							
								
								
									
										32
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -1,3 +1,35 @@
 | 
				
			|||||||
 | 
					**v123030751**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- debug: added a lot to pinpoint the url issue.
 | 
				
			||||||
 | 
					- feat: Use /sources/stats in the home (#133)
 | 
				
			||||||
 | 
					- Changelog for v123030681 [CI SKIP]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**v123030681**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- fix: Unread and starred can be null.
 | 
				
			||||||
 | 
					- Fixed version number issue.
 | 
				
			||||||
 | 
					- Changelog for v123030621 [CI SKIP]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**v123030621**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- fix: url required issue.
 | 
				
			||||||
 | 
					- fix: Canvas reused issue.
 | 
				
			||||||
 | 
					- Changelog for v123020572 [CI SKIP]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**v123020572**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- fix: requirecontext issues ?
 | 
				
			||||||
 | 
					- debug: activity not found exception.
 | 
				
			||||||
 | 
					- Changelog for v123020571 [CI SKIP]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**v123020571**
 | 
					**v123020571**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- chore: remove errors logging.
 | 
					- chore: remove errors logging.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,7 +28,7 @@ fun gitVersion(): String {
 | 
				
			|||||||
    val maybeTagOfCurrentCommit = execWithOutput("git -C ../ describe --contains HEAD", true)
 | 
					    val maybeTagOfCurrentCommit = execWithOutput("git -C ../ describe --contains HEAD", true)
 | 
				
			||||||
    process = if (maybeTagOfCurrentCommit.isEmpty()) {
 | 
					    process = if (maybeTagOfCurrentCommit.isEmpty()) {
 | 
				
			||||||
        println("No tag on current commit. Will take the latest one.")
 | 
					        println("No tag on current commit. Will take the latest one.")
 | 
				
			||||||
        execWithOutput("git -C ../ for-each-ref refs/tags --sort=-authordate --format='%(refname:short)' --count=1")
 | 
					        execWithOutput("git -C ../ for-each-ref refs/tags --sort=-refname --format='%(refname:short)' --count=1")
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        println("Tag found on current commit")
 | 
					        println("Tag found on current commit")
 | 
				
			||||||
        execWithOutput("git -C ../ describe --contains HEAD")
 | 
					        execWithOutput("git -C ../ describe --contains HEAD")
 | 
				
			||||||
@@ -142,11 +142,10 @@ dependencies {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // Material-ish things
 | 
					    // Material-ish things
 | 
				
			||||||
    implementation("com.ashokvarma.android:bottom-navigation-bar:2.2.0")
 | 
					    implementation("com.ashokvarma.android:bottom-navigation-bar:2.2.0")
 | 
				
			||||||
    implementation("com.amulyakhare:com.amulyakhare.textdrawable:1.0.1")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // glide
 | 
					    // glide
 | 
				
			||||||
    kapt("com.github.bumptech.glide:compiler:4.14.2")
 | 
					    kapt("com.github.bumptech.glide:compiler:4.15.0")
 | 
				
			||||||
    implementation("com.github.bumptech.glide:okhttp3-integration:4.14.2")
 | 
					    implementation("com.github.bumptech.glide:okhttp3-integration:4.15.0")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Themes
 | 
					    // Themes
 | 
				
			||||||
    implementation("com.github.rubensousa:floatingtoolbar:1.5.1")
 | 
					    implementation("com.github.rubensousa:floatingtoolbar:1.5.1")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -57,29 +57,7 @@ class LoginActivity : AppCompatActivity(), DIAware {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        if (appSettingsService.getBaseUrl().isNotEmpty()) {
 | 
					        if (appSettingsService.getBaseUrl().isNotEmpty()) {
 | 
				
			||||||
            showProgress(true)
 | 
					            showProgress(true)
 | 
				
			||||||
            // This should be reverted when "old" users connected with a non-selfoss rss
 | 
					            goToMain()
 | 
				
			||||||
            // are handled. Revert to "simple" way.
 | 
					 | 
				
			||||||
            CoroutineScope(Dispatchers.Main).launch {
 | 
					 | 
				
			||||||
                try {
 | 
					 | 
				
			||||||
                    val (errorFetching, displaySelfossOnly) = repository.shouldBeSelfossInstance()
 | 
					 | 
				
			||||||
                    if (!errorFetching && !displaySelfossOnly) {
 | 
					 | 
				
			||||||
                        goToMain()
 | 
					 | 
				
			||||||
                    } else {
 | 
					 | 
				
			||||||
                        showProgress(false)
 | 
					 | 
				
			||||||
                        if (displaySelfossOnly) {
 | 
					 | 
				
			||||||
                            Toast.makeText(
 | 
					 | 
				
			||||||
                                applicationContext,
 | 
					 | 
				
			||||||
                                R.string.application_selfoss_only,
 | 
					 | 
				
			||||||
                                Toast.LENGTH_LONG
 | 
					 | 
				
			||||||
                            ).show()
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                        repository.logout()
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                } catch (e: Throwable) {
 | 
					 | 
				
			||||||
                    repository.logout()
 | 
					 | 
				
			||||||
                    showProgress(false)
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        handleActions()
 | 
					        handleActions()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -49,13 +49,13 @@ class SourcesActivity : AppCompatActivity(), DIAware {
 | 
				
			|||||||
        super.onResume()
 | 
					        super.onResume()
 | 
				
			||||||
        val mLayoutManager = LinearLayoutManager(this)
 | 
					        val mLayoutManager = LinearLayoutManager(this)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var items: ArrayList<SelfossModel.Source>
 | 
					        var items: ArrayList<SelfossModel.SourceDetail>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        binding.recyclerView.setHasFixedSize(true)
 | 
					        binding.recyclerView.setHasFixedSize(true)
 | 
				
			||||||
        binding.recyclerView.layoutManager = mLayoutManager
 | 
					        binding.recyclerView.layoutManager = mLayoutManager
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        CoroutineScope(Dispatchers.Main).launch {
 | 
					        CoroutineScope(Dispatchers.Main).launch {
 | 
				
			||||||
            val response = repository.getSources()
 | 
					            val response = repository.getSourcesDetails()
 | 
				
			||||||
            if (response.isNotEmpty()) {
 | 
					            if (response.isNotEmpty()) {
 | 
				
			||||||
                items = response
 | 
					                items = response
 | 
				
			||||||
                val mAdapter = SourcesListAdapter(
 | 
					                val mAdapter = SourcesListAdapter(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,7 +24,7 @@ import org.kodein.di.instance
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class UpsertSourceActivity : AppCompatActivity(), DIAware {
 | 
					class UpsertSourceActivity : AppCompatActivity(), DIAware {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private var existingSource: SelfossModel.Source? = null
 | 
					    private var existingSource: SelfossModel.SourceDetail? = null
 | 
				
			||||||
    private var mSpoutsValue: String? = null
 | 
					    private var mSpoutsValue: String? = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private lateinit var binding: ActivityUpsertSourceBinding
 | 
					    private lateinit var binding: ActivityUpsertSourceBinding
 | 
				
			||||||
@@ -68,7 +68,7 @@ class UpsertSourceActivity : AppCompatActivity(), DIAware {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private fun initFields(items: Map<String, SelfossModel.Spout>) {
 | 
					    private fun initFields(items: Map<String, SelfossModel.Spout>) {
 | 
				
			||||||
        binding.nameInput.setText(existingSource!!.title)
 | 
					        binding.nameInput.setText(existingSource!!.title)
 | 
				
			||||||
        binding.tags.setText(existingSource!!.tags.joinToString(", "))
 | 
					        binding.tags.setText(existingSource!!.tags?.joinToString(", "))
 | 
				
			||||||
        binding.sourceUri.setText(existingSource!!.params?.url)
 | 
					        binding.sourceUri.setText(existingSource!!.params?.url)
 | 
				
			||||||
        binding.spoutsSpinner.setSelection(items.keys.indexOf(existingSource!!.spout))
 | 
					        binding.spoutsSpinner.setSelection(items.keys.indexOf(existingSource!!.spout))
 | 
				
			||||||
        binding.progress.visibility = View.GONE
 | 
					        binding.progress.visibility = View.GONE
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,10 +9,9 @@ import android.widget.ImageView.ScaleType
 | 
				
			|||||||
import androidx.recyclerview.widget.RecyclerView
 | 
					import androidx.recyclerview.widget.RecyclerView
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.android.R
 | 
					import bou.amine.apps.readerforselfossv2.android.R
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.android.databinding.CardItemBinding
 | 
					import bou.amine.apps.readerforselfossv2.android.databinding.CardItemBinding
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.android.model.toTextDrawableString
 | 
					 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.android.utils.LinkOnTouchListener
 | 
					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.bitmapCenterCrop
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularBitmapDrawable
 | 
					import bou.amine.apps.readerforselfossv2.android.utils.glide.circularDrawable
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.android.utils.openInBrowserAsNewTask
 | 
					import bou.amine.apps.readerforselfossv2.android.utils.openInBrowserAsNewTask
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.android.utils.openItemUrl
 | 
					import bou.amine.apps.readerforselfossv2.android.utils.openItemUrl
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.android.utils.shareLink
 | 
					import bou.amine.apps.readerforselfossv2.android.utils.shareLink
 | 
				
			||||||
@@ -22,8 +21,6 @@ import bou.amine.apps.readerforselfossv2.service.AppSettingsService
 | 
				
			|||||||
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
 | 
					import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.utils.getIcon
 | 
					import bou.amine.apps.readerforselfossv2.utils.getIcon
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.utils.getThumbnail
 | 
					import bou.amine.apps.readerforselfossv2.utils.getThumbnail
 | 
				
			||||||
import com.amulyakhare.textdrawable.TextDrawable
 | 
					 | 
				
			||||||
import com.amulyakhare.textdrawable.util.ColorGenerator
 | 
					 | 
				
			||||||
import com.bumptech.glide.Glide
 | 
					import com.bumptech.glide.Glide
 | 
				
			||||||
import kotlinx.coroutines.CoroutineScope
 | 
					import kotlinx.coroutines.CoroutineScope
 | 
				
			||||||
import kotlinx.coroutines.Dispatchers
 | 
					import kotlinx.coroutines.Dispatchers
 | 
				
			||||||
@@ -38,7 +35,6 @@ class ItemCardAdapter(
 | 
				
			|||||||
    override val updateItems: (ArrayList<SelfossModel.Item>) -> Unit
 | 
					    override val updateItems: (ArrayList<SelfossModel.Item>) -> Unit
 | 
				
			||||||
) : ItemsAdapter<ItemCardAdapter.ViewHolder>() {
 | 
					) : ItemsAdapter<ItemCardAdapter.ViewHolder>() {
 | 
				
			||||||
    private val c: Context = app.baseContext
 | 
					    private val c: Context = app.baseContext
 | 
				
			||||||
    private val generator: ColorGenerator = ColorGenerator.MATERIAL
 | 
					 | 
				
			||||||
    private val imageMaxHeight: Int =
 | 
					    private val imageMaxHeight: Int =
 | 
				
			||||||
        c.resources.getDimension(R.dimen.card_image_max_height).toInt()
 | 
					        c.resources.getDimension(R.dimen.card_image_max_height).toInt()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -83,16 +79,9 @@ class ItemCardAdapter(
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (itm.getIcon(repository.baseUrl).isEmpty()) {
 | 
					            if (itm.getIcon(repository.baseUrl).isEmpty()) {
 | 
				
			||||||
                val color = generator.getColor(itm.title.getHtmlDecoded())
 | 
					                binding.sourceImage.setBackgroundAndText(itm.sourcetitle.getHtmlDecoded())
 | 
				
			||||||
 | 
					 | 
				
			||||||
                val drawable =
 | 
					 | 
				
			||||||
                    TextDrawable
 | 
					 | 
				
			||||||
                        .builder()
 | 
					 | 
				
			||||||
                        .round()
 | 
					 | 
				
			||||||
                        .build(itm.title.getHtmlDecoded().toTextDrawableString(), color)
 | 
					 | 
				
			||||||
                binding.sourceImage.setImageDrawable(drawable)
 | 
					 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                c.circularBitmapDrawable(itm.getIcon(repository.baseUrl), binding.sourceImage)
 | 
					                c.circularDrawable(itm.getIcon(repository.baseUrl), binding.sourceImage)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,10 +7,8 @@ import android.view.ViewGroup
 | 
				
			|||||||
import androidx.recyclerview.widget.RecyclerView
 | 
					import androidx.recyclerview.widget.RecyclerView
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.android.R
 | 
					import bou.amine.apps.readerforselfossv2.android.R
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.android.databinding.ListItemBinding
 | 
					import bou.amine.apps.readerforselfossv2.android.databinding.ListItemBinding
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.android.model.toTextDrawableString
 | 
					 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.android.utils.LinkOnTouchListener
 | 
					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.glide.circularBitmapDrawable
 | 
					 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.android.utils.openItemUrl
 | 
					import bou.amine.apps.readerforselfossv2.android.utils.openItemUrl
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
 | 
					import bou.amine.apps.readerforselfossv2.model.SelfossModel
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.repository.Repository
 | 
					import bou.amine.apps.readerforselfossv2.repository.Repository
 | 
				
			||||||
@@ -18,8 +16,6 @@ import bou.amine.apps.readerforselfossv2.service.AppSettingsService
 | 
				
			|||||||
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
 | 
					import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.utils.getIcon
 | 
					import bou.amine.apps.readerforselfossv2.utils.getIcon
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.utils.getThumbnail
 | 
					import bou.amine.apps.readerforselfossv2.utils.getThumbnail
 | 
				
			||||||
import com.amulyakhare.textdrawable.TextDrawable
 | 
					 | 
				
			||||||
import com.amulyakhare.textdrawable.util.ColorGenerator
 | 
					 | 
				
			||||||
import org.kodein.di.DI
 | 
					import org.kodein.di.DI
 | 
				
			||||||
import org.kodein.di.android.closestDI
 | 
					import org.kodein.di.android.closestDI
 | 
				
			||||||
import org.kodein.di.instance
 | 
					import org.kodein.di.instance
 | 
				
			||||||
@@ -29,7 +25,6 @@ class ItemListAdapter(
 | 
				
			|||||||
    override var items: ArrayList<SelfossModel.Item>,
 | 
					    override var items: ArrayList<SelfossModel.Item>,
 | 
				
			||||||
    override val updateItems: (ArrayList<SelfossModel.Item>) -> Unit
 | 
					    override val updateItems: (ArrayList<SelfossModel.Item>) -> Unit
 | 
				
			||||||
) : ItemsAdapter<ItemListAdapter.ViewHolder>() {
 | 
					) : ItemsAdapter<ItemListAdapter.ViewHolder>() {
 | 
				
			||||||
    private val generator: ColorGenerator = ColorGenerator.MATERIAL
 | 
					 | 
				
			||||||
    private val c: Context = app.baseContext
 | 
					    private val c: Context = app.baseContext
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override val di: DI by closestDI(app)
 | 
					    override val di: DI by closestDI(app)
 | 
				
			||||||
@@ -56,20 +51,12 @@ class ItemListAdapter(
 | 
				
			|||||||
            if (itm.getThumbnail(repository.baseUrl).isEmpty()) {
 | 
					            if (itm.getThumbnail(repository.baseUrl).isEmpty()) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (itm.getIcon(repository.baseUrl).isEmpty()) {
 | 
					                if (itm.getIcon(repository.baseUrl).isEmpty()) {
 | 
				
			||||||
                    val color = generator.getColor(itm.title.getHtmlDecoded())
 | 
					                    binding.itemImage.setBackgroundAndText(itm.sourcetitle.getHtmlDecoded())
 | 
				
			||||||
 | 
					 | 
				
			||||||
                    val drawable =
 | 
					 | 
				
			||||||
                            TextDrawable
 | 
					 | 
				
			||||||
                                    .builder()
 | 
					 | 
				
			||||||
                                    .round()
 | 
					 | 
				
			||||||
                                    .build(itm.title.getHtmlDecoded().toTextDrawableString(), color)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    binding.itemImage.setImageDrawable(drawable)
 | 
					 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    c.circularBitmapDrawable(itm.getIcon(repository.baseUrl), binding.itemImage)
 | 
					                    c.circularDrawable(itm.getIcon(repository.baseUrl), binding.itemImage)
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                c.bitmapCenterCrop(itm.getThumbnail(repository.baseUrl), binding.itemImage)
 | 
					                c.circularDrawable(itm.getThumbnail(repository.baseUrl), binding.itemImage)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,17 +10,14 @@ import android.widget.Button
 | 
				
			|||||||
import android.widget.Toast
 | 
					import android.widget.Toast
 | 
				
			||||||
import androidx.constraintlayout.widget.ConstraintLayout
 | 
					import androidx.constraintlayout.widget.ConstraintLayout
 | 
				
			||||||
import androidx.recyclerview.widget.RecyclerView
 | 
					import androidx.recyclerview.widget.RecyclerView
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.android.UpsertSourceActivity
 | 
					 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.android.R
 | 
					import bou.amine.apps.readerforselfossv2.android.R
 | 
				
			||||||
 | 
					import bou.amine.apps.readerforselfossv2.android.UpsertSourceActivity
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.android.databinding.SourceListItemBinding
 | 
					import bou.amine.apps.readerforselfossv2.android.databinding.SourceListItemBinding
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.android.model.toTextDrawableString
 | 
					import bou.amine.apps.readerforselfossv2.android.utils.glide.circularDrawable
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularBitmapDrawable
 | 
					 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
 | 
					import bou.amine.apps.readerforselfossv2.model.SelfossModel
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.repository.Repository
 | 
					import bou.amine.apps.readerforselfossv2.repository.Repository
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
 | 
					import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.utils.getIcon
 | 
					import bou.amine.apps.readerforselfossv2.utils.getIcon
 | 
				
			||||||
import com.amulyakhare.textdrawable.TextDrawable
 | 
					 | 
				
			||||||
import com.amulyakhare.textdrawable.util.ColorGenerator
 | 
					 | 
				
			||||||
import kotlinx.coroutines.CoroutineScope
 | 
					import kotlinx.coroutines.CoroutineScope
 | 
				
			||||||
import kotlinx.coroutines.Dispatchers
 | 
					import kotlinx.coroutines.Dispatchers
 | 
				
			||||||
import kotlinx.coroutines.launch
 | 
					import kotlinx.coroutines.launch
 | 
				
			||||||
@@ -31,10 +28,9 @@ import org.kodein.di.instance
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class SourcesListAdapter(
 | 
					class SourcesListAdapter(
 | 
				
			||||||
    private val app: Activity,
 | 
					    private val app: Activity,
 | 
				
			||||||
    private val items: ArrayList<SelfossModel.Source>
 | 
					    private val items: ArrayList<SelfossModel.SourceDetail>
 | 
				
			||||||
) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>(), DIAware {
 | 
					) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>(), DIAware {
 | 
				
			||||||
    private val c: Context = app.baseContext
 | 
					    private val c: Context = app.baseContext
 | 
				
			||||||
    private val generator: ColorGenerator = ColorGenerator.MATERIAL
 | 
					 | 
				
			||||||
    private lateinit var binding: SourceListItemBinding
 | 
					    private lateinit var binding: SourceListItemBinding
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override val di: DI by closestDI(app)
 | 
					    override val di: DI by closestDI(app)
 | 
				
			||||||
@@ -49,19 +45,12 @@ class SourcesListAdapter(
 | 
				
			|||||||
        val itm = items[position]
 | 
					        val itm = items[position]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (itm.getIcon(repository.baseUrl).isEmpty()) {
 | 
					        if (itm.getIcon(repository.baseUrl).isEmpty()) {
 | 
				
			||||||
            val color = generator.getColor(itm.title.getHtmlDecoded())
 | 
					            binding.itemImage.setBackgroundAndText(itm.title.getHtmlDecoded())
 | 
				
			||||||
 | 
					 | 
				
			||||||
            val drawable =
 | 
					 | 
				
			||||||
                TextDrawable
 | 
					 | 
				
			||||||
                    .builder()
 | 
					 | 
				
			||||||
                    .round()
 | 
					 | 
				
			||||||
                    .build(itm.title.getHtmlDecoded().toTextDrawableString(), color)
 | 
					 | 
				
			||||||
            binding.itemImage.setImageDrawable(drawable)
 | 
					 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            c.circularBitmapDrawable(itm.getIcon(repository.baseUrl), binding.itemImage)
 | 
					            c.circularDrawable(itm.getIcon(repository.baseUrl), binding.itemImage)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (itm.error.isNotBlank()) {
 | 
					        if (!itm.error.isNullOrBlank()) {
 | 
				
			||||||
            binding.errorText.visibility = View.VISIBLE
 | 
					            binding.errorText.visibility = View.VISIBLE
 | 
				
			||||||
            binding.errorText.text = itm.error
 | 
					            binding.errorText.text = itm.error
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
package bou.amine.apps.readerforselfossv2.android.fragments
 | 
					package bou.amine.apps.readerforselfossv2.android.fragments
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.content.ActivityNotFoundException
 | 
				
			||||||
import android.content.Intent
 | 
					import android.content.Intent
 | 
				
			||||||
import android.content.res.ColorStateList
 | 
					import android.content.res.ColorStateList
 | 
				
			||||||
import android.content.res.TypedArray
 | 
					import android.content.res.TypedArray
 | 
				
			||||||
@@ -150,17 +151,19 @@ class ArticleFragment : Fragment(), DIAware {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        } catch (e: InflateException) {
 | 
					        } catch (e: InflateException) {
 | 
				
			||||||
            e.sendSilentlyWithAcraWithName("webview not available")
 | 
					            e.sendSilentlyWithAcraWithName("webview not available")
 | 
				
			||||||
            AlertDialog.Builder(requireContext())
 | 
					            if (context != null) {
 | 
				
			||||||
                .setMessage(requireContext().getString(R.string.webview_dialog_issue_message))
 | 
					                AlertDialog.Builder(requireContext())
 | 
				
			||||||
                .setTitle(requireContext().getString(R.string.webview_dialog_issue_title))
 | 
					                    .setMessage(requireContext().getString(R.string.webview_dialog_issue_message))
 | 
				
			||||||
                .setPositiveButton(
 | 
					                    .setTitle(requireContext().getString(R.string.webview_dialog_issue_title))
 | 
				
			||||||
                    android.R.string.ok
 | 
					                    .setPositiveButton(
 | 
				
			||||||
                ) { _, _ ->
 | 
					                        android.R.string.ok
 | 
				
			||||||
                    appSettingsService.disableArticleViewer()
 | 
					                    ) { _, _ ->
 | 
				
			||||||
                    requireActivity().finish()
 | 
					                        appSettingsService.disableArticleViewer()
 | 
				
			||||||
                }
 | 
					                        requireActivity().finish()
 | 
				
			||||||
                .create()
 | 
					                    }
 | 
				
			||||||
                .show()
 | 
					                    .create()
 | 
				
			||||||
 | 
					                    .show()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return binding.root
 | 
					        return binding.root
 | 
				
			||||||
@@ -304,10 +307,16 @@ class ArticleFragment : Fragment(), DIAware {
 | 
				
			|||||||
        binding.webcontent.webViewClient = object : WebViewClient() {
 | 
					        binding.webcontent.webViewClient = object : WebViewClient() {
 | 
				
			||||||
            @Deprecated("Deprecated in Java")
 | 
					            @Deprecated("Deprecated in Java")
 | 
				
			||||||
            override fun shouldOverrideUrlLoading(view: WebView?, url: String): Boolean {
 | 
					            override fun shouldOverrideUrlLoading(view: WebView?, url: String): Boolean {
 | 
				
			||||||
                if (binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
 | 
					                return if (context != null && binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
 | 
				
			||||||
                    requireContext().startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
 | 
					                    try {
 | 
				
			||||||
 | 
					                        requireContext().startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
 | 
				
			||||||
 | 
					                    } catch (e: ActivityNotFoundException) {
 | 
				
			||||||
 | 
					                        e.sendSilentlyWithAcraWithName("activityNotFound > $url")
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    true
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    false
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                return true
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            @Deprecated("Deprecated in Java")
 | 
					            @Deprecated("Deprecated in Java")
 | 
				
			||||||
@@ -359,74 +368,74 @@ class ArticleFragment : Fragment(), DIAware {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun htmlToWebview() {
 | 
					    private fun htmlToWebview() {
 | 
				
			||||||
 | 
					        if (context != null) {
 | 
				
			||||||
        val attrs: IntArray = intArrayOf(android.R.attr.fontFamily)
 | 
					            val attrs: IntArray = intArrayOf(android.R.attr.fontFamily)
 | 
				
			||||||
        val a: TypedArray = requireContext().obtainStyledAttributes(resId, attrs)
 | 
					            val a: TypedArray = requireContext().obtainStyledAttributes(resId, attrs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        binding.webcontent.settings.standardFontFamily = a.getString(0)
 | 
					            binding.webcontent.settings.standardFontFamily = a.getString(0)
 | 
				
			||||||
        binding.webcontent.visibility = View.VISIBLE
 | 
					            binding.webcontent.visibility = View.VISIBLE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val colorOnSurface = TypedValue()
 | 
					            val colorOnSurface = TypedValue()
 | 
				
			||||||
        requireContext().theme.resolveAttribute(R.attr.colorOnSurface, colorOnSurface, true)
 | 
					            requireContext().theme.resolveAttribute(R.attr.colorOnSurface, colorOnSurface, true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val colorSurface = TypedValue()
 | 
					            val colorSurface = TypedValue()
 | 
				
			||||||
        requireContext().theme.resolveAttribute(R.attr.colorSurface, colorSurface, true)
 | 
					            requireContext().theme.resolveAttribute(R.attr.colorSurface, colorSurface, true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        binding.webcontent.settings.useWideViewPort = true
 | 
					            binding.webcontent.settings.useWideViewPort = true
 | 
				
			||||||
        binding.webcontent.settings.loadWithOverviewMode = true
 | 
					            binding.webcontent.settings.loadWithOverviewMode = true
 | 
				
			||||||
        binding.webcontent.settings.javaScriptEnabled = false
 | 
					            binding.webcontent.settings.javaScriptEnabled = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        handleImageLoading()
 | 
					            handleImageLoading()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val gestureDetector =
 | 
					            val gestureDetector =
 | 
				
			||||||
            GestureDetector(activity, object : GestureDetector.SimpleOnGestureListener() {
 | 
					                GestureDetector(activity, object : GestureDetector.SimpleOnGestureListener() {
 | 
				
			||||||
                override fun onSingleTapUp(e: MotionEvent): Boolean {
 | 
					                    override fun onSingleTapUp(e: MotionEvent): Boolean {
 | 
				
			||||||
                    return performClick()
 | 
					                        return performClick()
 | 
				
			||||||
                }
 | 
					                    }
 | 
				
			||||||
            })
 | 
					                })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        binding.webcontent.setOnTouchListener { _, event -> gestureDetector.onTouchEvent(event) }
 | 
					            binding.webcontent.setOnTouchListener { _, event -> gestureDetector.onTouchEvent(event) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        binding.webcontent.settings.layoutAlgorithm =
 | 
					            binding.webcontent.settings.layoutAlgorithm =
 | 
				
			||||||
            WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
 | 
					                WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var baseUrl: String? = null
 | 
					            var baseUrl: String? = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try {
 | 
					            try {
 | 
				
			||||||
            val itemUrl = URL(url)
 | 
					                val itemUrl = URL(url)
 | 
				
			||||||
            baseUrl = itemUrl.protocol + "://" + itemUrl.host
 | 
					                baseUrl = itemUrl.protocol + "://" + itemUrl.host
 | 
				
			||||||
        } catch (e: MalformedURLException) {
 | 
					            } catch (e: MalformedURLException) {
 | 
				
			||||||
            e.sendSilentlyWithAcraWithName("htmlToWebview > item url")
 | 
					                e.sendSilentlyWithAcraWithName("htmlToWebview > $url")
 | 
				
			||||||
        }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val fontName = when (font) {
 | 
					            val fontName = when (font) {
 | 
				
			||||||
            getString(R.string.open_sans_font_id) -> "Open Sans"
 | 
					                getString(R.string.open_sans_font_id) -> "Open Sans"
 | 
				
			||||||
            getString(R.string.roboto_font_id) -> "Roboto"
 | 
					                getString(R.string.roboto_font_id) -> "Roboto"
 | 
				
			||||||
            getString(R.string.source_code_pro_font_id) -> "Source Code Pro"
 | 
					                getString(R.string.source_code_pro_font_id) -> "Source Code Pro"
 | 
				
			||||||
            else -> ""
 | 
					                else -> ""
 | 
				
			||||||
        }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val fontLinkAndStyle = if (font.isNotEmpty()) {
 | 
					            val fontLinkAndStyle = if (font.isNotEmpty()) {
 | 
				
			||||||
            """<link href="https://fonts.googleapis.com/css?family=${
 | 
					                """<link href="https://fonts.googleapis.com/css?family=${
 | 
				
			||||||
                fontName.replace(
 | 
					                    fontName.replace(
 | 
				
			||||||
                    " ",
 | 
					                        " ",
 | 
				
			||||||
                    "+"
 | 
					                        "+"
 | 
				
			||||||
                )
 | 
					                    )
 | 
				
			||||||
            }" rel="stylesheet">
 | 
					                }" rel="stylesheet">
 | 
				
			||||||
                |<style>
 | 
					                |<style>
 | 
				
			||||||
                |   * {
 | 
					                |   * {
 | 
				
			||||||
                |       font-family: '$fontName';
 | 
					                |       font-family: '$fontName';
 | 
				
			||||||
                |   }
 | 
					                |   }
 | 
				
			||||||
                |</style>
 | 
					                |</style>
 | 
				
			||||||
            """.trimMargin()
 | 
					            """.trimMargin()
 | 
				
			||||||
        } else {
 | 
					            } else {
 | 
				
			||||||
            ""
 | 
					                ""
 | 
				
			||||||
        }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        binding.webcontent.loadDataWithBaseURL(
 | 
					            binding.webcontent.loadDataWithBaseURL(
 | 
				
			||||||
            baseUrl,
 | 
					                baseUrl,
 | 
				
			||||||
            """<html>
 | 
					                """<html>
 | 
				
			||||||
                |<head>
 | 
					                |<head>
 | 
				
			||||||
                |   <meta name="viewport" content="width=device-width, initial-scale=1">
 | 
					                |   <meta name="viewport" content="width=device-width, initial-scale=1">
 | 
				
			||||||
                |   <style>
 | 
					                |   <style>
 | 
				
			||||||
@@ -438,11 +447,11 @@ class ArticleFragment : Fragment(), DIAware {
 | 
				
			|||||||
                |      }
 | 
					                |      }
 | 
				
			||||||
                |      a {
 | 
					                |      a {
 | 
				
			||||||
                |        color: ${
 | 
					                |        color: ${
 | 
				
			||||||
                String.format(
 | 
					                    String.format(
 | 
				
			||||||
                    "#%06X",
 | 
					                        "#%06X",
 | 
				
			||||||
                    0xFFFFFF and resources.getColor(R.color.colorAccent)
 | 
					                        0xFFFFFF and resources.getColor(R.color.colorAccent)
 | 
				
			||||||
                )
 | 
					                    )
 | 
				
			||||||
            } !important;
 | 
					                } !important;
 | 
				
			||||||
                |      }
 | 
					                |      }
 | 
				
			||||||
                |      *:not(a) {
 | 
					                |      *:not(a) {
 | 
				
			||||||
                |        color: ${String.format("#%06X", 0xFFFFFF and colorOnSurface.data)};
 | 
					                |        color: ${String.format("#%06X", 0xFFFFFF and colorOnSurface.data)};
 | 
				
			||||||
@@ -454,25 +463,25 @@ class ArticleFragment : Fragment(), DIAware {
 | 
				
			|||||||
                |        overflow:hidden;
 | 
					                |        overflow:hidden;
 | 
				
			||||||
                |        line-height: 1.5em;
 | 
					                |        line-height: 1.5em;
 | 
				
			||||||
                |        background-color: ${
 | 
					                |        background-color: ${
 | 
				
			||||||
                String.format(
 | 
					                    String.format(
 | 
				
			||||||
                    "#%06X",
 | 
					                        "#%06X",
 | 
				
			||||||
                    0xFFFFFF and colorSurface.data
 | 
					                        0xFFFFFF and colorSurface.data
 | 
				
			||||||
                )
 | 
					                    )
 | 
				
			||||||
            };
 | 
					                };
 | 
				
			||||||
                |      }
 | 
					                |      }
 | 
				
			||||||
                |      body, html {
 | 
					                |      body, html {
 | 
				
			||||||
                |        background-color: ${
 | 
					                |        background-color: ${
 | 
				
			||||||
                String.format(
 | 
					                    String.format(
 | 
				
			||||||
                    "#%06X",
 | 
					                        "#%06X",
 | 
				
			||||||
                    0xFFFFFF and colorSurface.data
 | 
					                        0xFFFFFF and colorSurface.data
 | 
				
			||||||
                )
 | 
					                    )
 | 
				
			||||||
            } !important;
 | 
					                } !important;
 | 
				
			||||||
                |        border-color: ${
 | 
					                |        border-color: ${
 | 
				
			||||||
                String.format(
 | 
					                    String.format(
 | 
				
			||||||
                    "#%06X",
 | 
					                        "#%06X",
 | 
				
			||||||
                    0xFFFFFF and colorSurface.data
 | 
					                        0xFFFFFF and colorSurface.data
 | 
				
			||||||
                )
 | 
					                    )
 | 
				
			||||||
            }  !important;
 | 
					                }  !important;
 | 
				
			||||||
                |        padding: 0 !important;
 | 
					                |        padding: 0 !important;
 | 
				
			||||||
                |        margin: 0 !important;
 | 
					                |        margin: 0 !important;
 | 
				
			||||||
                |      }
 | 
					                |      }
 | 
				
			||||||
@@ -483,11 +492,11 @@ class ArticleFragment : Fragment(), DIAware {
 | 
				
			|||||||
                |        white-space: pre-wrap;
 | 
					                |        white-space: pre-wrap;
 | 
				
			||||||
                |        width:100%;
 | 
					                |        width:100%;
 | 
				
			||||||
                |        background-color: ${
 | 
					                |        background-color: ${
 | 
				
			||||||
                String.format(
 | 
					                    String.format(
 | 
				
			||||||
                    "#%06X",
 | 
					                        "#%06X",
 | 
				
			||||||
                    0xFFFFFF and colorSurface.data
 | 
					                        0xFFFFFF and colorSurface.data
 | 
				
			||||||
                )
 | 
					                    )
 | 
				
			||||||
            };
 | 
					                };
 | 
				
			||||||
                |      }
 | 
					                |      }
 | 
				
			||||||
                |   </style>
 | 
					                |   </style>
 | 
				
			||||||
                |   $fontLinkAndStyle
 | 
					                |   $fontLinkAndStyle
 | 
				
			||||||
@@ -495,10 +504,11 @@ class ArticleFragment : Fragment(), DIAware {
 | 
				
			|||||||
                |<body>
 | 
					                |<body>
 | 
				
			||||||
                |   $contentText
 | 
					                |   $contentText
 | 
				
			||||||
                |</body>""".trimMargin(),
 | 
					                |</body>""".trimMargin(),
 | 
				
			||||||
            "text/html",
 | 
					                "text/html",
 | 
				
			||||||
            "utf-8",
 | 
					                "utf-8",
 | 
				
			||||||
            null
 | 
					                null
 | 
				
			||||||
        )
 | 
					            )
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun scrollDown() {
 | 
					    fun scrollDown() {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,10 +20,8 @@ import bou.amine.apps.readerforselfossv2.repository.Repository
 | 
				
			|||||||
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
 | 
					import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
 | 
				
			||||||
import bou.amine.apps.readerforselfossv2.utils.getIcon
 | 
					import bou.amine.apps.readerforselfossv2.utils.getIcon
 | 
				
			||||||
import com.bumptech.glide.Glide
 | 
					import com.bumptech.glide.Glide
 | 
				
			||||||
import com.bumptech.glide.load.DataSource
 | 
					import com.bumptech.glide.request.target.ViewTarget
 | 
				
			||||||
import com.bumptech.glide.load.engine.GlideException
 | 
					import com.bumptech.glide.request.transition.Transition
 | 
				
			||||||
import com.bumptech.glide.request.RequestListener
 | 
					 | 
				
			||||||
import com.bumptech.glide.request.target.Target
 | 
					 | 
				
			||||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
 | 
					import com.google.android.material.bottomsheet.BottomSheetDialogFragment
 | 
				
			||||||
import com.google.android.material.chip.Chip
 | 
					import com.google.android.material.chip.Chip
 | 
				
			||||||
import kotlinx.coroutines.CoroutineScope
 | 
					import kotlinx.coroutines.CoroutineScope
 | 
				
			||||||
@@ -84,37 +82,25 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
 | 
				
			|||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        val sourceGroup = binding.sourcesGroup
 | 
					        val sourceGroup = binding.sourcesGroup
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        repository.getSources().forEach { source ->
 | 
					        repository.getSourcesDetailsOrStats().forEach { source ->
 | 
				
			||||||
            val c = Chip(context)
 | 
					            val c = Chip(context)
 | 
				
			||||||
            c.ellipsize = TextUtils.TruncateAt.END
 | 
					            c.ellipsize = TextUtils.TruncateAt.END
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            Glide.with(context)
 | 
					            Glide.with(context)
 | 
				
			||||||
                .load(source.getIcon(repository.baseUrl))
 | 
					                .load(source.getIcon(repository.baseUrl))
 | 
				
			||||||
                .listener(object : RequestListener<Drawable?> {
 | 
					                .into(object : ViewTarget<Chip?, Drawable?>(c) {
 | 
				
			||||||
                    override fun onLoadFailed(
 | 
					 | 
				
			||||||
                        e: GlideException?,
 | 
					 | 
				
			||||||
                        model: Any?,
 | 
					 | 
				
			||||||
                        target: Target<Drawable?>?,
 | 
					 | 
				
			||||||
                        isFirstResource: Boolean
 | 
					 | 
				
			||||||
                    ): Boolean {
 | 
					 | 
				
			||||||
                        return false
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    override fun onResourceReady(
 | 
					                    override fun onResourceReady(
 | 
				
			||||||
                        resource: Drawable?,
 | 
					                        resource: Drawable,
 | 
				
			||||||
                        model: Any?,
 | 
					                        transition: Transition<in Drawable?>?
 | 
				
			||||||
                        target: Target<Drawable?>?,
 | 
					                    ) {
 | 
				
			||||||
                        dataSource: DataSource?,
 | 
					 | 
				
			||||||
                        isFirstResource: Boolean
 | 
					 | 
				
			||||||
                    ): Boolean {
 | 
					 | 
				
			||||||
                        try {
 | 
					                        try {
 | 
				
			||||||
                            c.chipIcon = resource
 | 
					                            c.chipIcon = resource
 | 
				
			||||||
                        } catch (e: Exception) {
 | 
					                        } catch (e: Exception) {
 | 
				
			||||||
                            e.sendSilentlyWithAcraWithName("sources > onResourceReady")
 | 
					                            e.sendSilentlyWithAcraWithName("sources > onResourceReady")
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        return false
 | 
					 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }).preload()
 | 
					
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            c.text = source.title.getHtmlDecoded()
 | 
					            c.text = source.title.getHtmlDecoded()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -141,9 +127,9 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
 | 
				
			|||||||
                selectedChip = c
 | 
					                selectedChip = c
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            c.isEnabled = source.error.isBlank()
 | 
					            c.isEnabled = source.error.isNullOrBlank()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (source.error.isNotBlank() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
 | 
					            if (!source.error.isNullOrBlank() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
 | 
				
			||||||
                c.tooltipText = source.error
 | 
					                c.tooltipText = source.error
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,62 @@
 | 
				
			|||||||
 | 
					package bou.amine.apps.readerforselfossv2.android.utils
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.content.Context
 | 
				
			||||||
 | 
					import android.graphics.drawable.GradientDrawable
 | 
				
			||||||
 | 
					import android.util.AttributeSet
 | 
				
			||||||
 | 
					import android.view.LayoutInflater
 | 
				
			||||||
 | 
					import android.view.View
 | 
				
			||||||
 | 
					import android.widget.RelativeLayout
 | 
				
			||||||
 | 
					import android.widget.TextView
 | 
				
			||||||
 | 
					import bou.amine.apps.readerforselfossv2.android.R
 | 
				
			||||||
 | 
					import bou.amine.apps.readerforselfossv2.android.model.toTextDrawableString
 | 
				
			||||||
 | 
					import com.google.android.material.imageview.ShapeableImageView
 | 
				
			||||||
 | 
					import kotlin.math.abs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CircleImageView @JvmOverloads constructor(
 | 
				
			||||||
 | 
					    context: Context,
 | 
				
			||||||
 | 
					    attrs: AttributeSet? = null,
 | 
				
			||||||
 | 
					    defStyleAttr: Int = 0
 | 
				
			||||||
 | 
					) : RelativeLayout(context, attrs, defStyleAttr) {
 | 
				
			||||||
 | 
					    val view: View
 | 
				
			||||||
 | 
					    val imageView: ShapeableImageView
 | 
				
			||||||
 | 
					    val textView: TextView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private val colorScheme = listOf(
 | 
				
			||||||
 | 
					    -0x1a8c8d,
 | 
				
			||||||
 | 
					    -0xf9d6e,
 | 
				
			||||||
 | 
					    -0x459738,
 | 
				
			||||||
 | 
					    -0x6a8a33,
 | 
				
			||||||
 | 
					    -0x867935,
 | 
				
			||||||
 | 
					    -0x9b4a0a,
 | 
				
			||||||
 | 
					    -0xb03c09,
 | 
				
			||||||
 | 
					    -0xb22f1f,
 | 
				
			||||||
 | 
					    -0xb24954,
 | 
				
			||||||
 | 
					    -0x7e387c,
 | 
				
			||||||
 | 
					    -0x512a7f,
 | 
				
			||||||
 | 
					    -0x759b,
 | 
				
			||||||
 | 
					    -0x2b1ea9,
 | 
				
			||||||
 | 
					    -0x2ab1,
 | 
				
			||||||
 | 
					    -0x48b3,
 | 
				
			||||||
 | 
					    -0x5e7781,
 | 
				
			||||||
 | 
					    -0x6f5b52
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    init {
 | 
				
			||||||
 | 
					        view = LayoutInflater.from(context).inflate(R.layout.circle_image_view, this, true)
 | 
				
			||||||
 | 
					        imageView = view.findViewById(R.id.circleImage)
 | 
				
			||||||
 | 
					        textView = view.findViewById(R.id.circleText)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun setBackgroundAndText(text: String) {
 | 
				
			||||||
 | 
					        val circleDrawable = GradientDrawable()
 | 
				
			||||||
 | 
					        val color = colorFromIdentifier(text)
 | 
				
			||||||
 | 
					        circleDrawable.setColor(color)
 | 
				
			||||||
 | 
					        imageView.setImageDrawable(circleDrawable)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        textView.text = text.toTextDrawableString()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun colorFromIdentifier(key: String): Int {
 | 
				
			||||||
 | 
					        return colorScheme[abs(key.hashCode()) % colorScheme.size]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -3,10 +3,9 @@ package bou.amine.apps.readerforselfossv2.android.utils.glide
 | 
				
			|||||||
import android.content.Context
 | 
					import android.content.Context
 | 
				
			||||||
import android.graphics.Bitmap
 | 
					import android.graphics.Bitmap
 | 
				
			||||||
import android.widget.ImageView
 | 
					import android.widget.ImageView
 | 
				
			||||||
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory
 | 
					import bou.amine.apps.readerforselfossv2.android.utils.CircleImageView
 | 
				
			||||||
import com.bumptech.glide.Glide
 | 
					import com.bumptech.glide.Glide
 | 
				
			||||||
import com.bumptech.glide.request.RequestOptions
 | 
					import com.bumptech.glide.request.RequestOptions
 | 
				
			||||||
import com.bumptech.glide.request.target.BitmapImageViewTarget
 | 
					 | 
				
			||||||
import java.io.ByteArrayInputStream
 | 
					import java.io.ByteArrayInputStream
 | 
				
			||||||
import java.io.ByteArrayOutputStream
 | 
					import java.io.ByteArrayOutputStream
 | 
				
			||||||
import java.io.InputStream
 | 
					import java.io.InputStream
 | 
				
			||||||
@@ -18,21 +17,13 @@ fun Context.bitmapCenterCrop(url: String, iv: ImageView) =
 | 
				
			|||||||
        .apply(RequestOptions.centerCropTransform())
 | 
					        .apply(RequestOptions.centerCropTransform())
 | 
				
			||||||
        .into(iv)
 | 
					        .into(iv)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fun Context.circularBitmapDrawable(url: String, iv: ImageView) =
 | 
					fun Context.circularDrawable(url: String, view: CircleImageView) {
 | 
				
			||||||
 | 
					    view.textView.text =""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Glide.with(this)
 | 
					    Glide.with(this)
 | 
				
			||||||
        .asBitmap()
 | 
					 | 
				
			||||||
        .load(url)
 | 
					        .load(url)
 | 
				
			||||||
        .apply(RequestOptions.centerCropTransform())
 | 
					        .into(view.imageView)
 | 
				
			||||||
        .into(object : BitmapImageViewTarget(iv) {
 | 
					}
 | 
				
			||||||
            override fun setResource(resource: Bitmap?) {
 | 
					 | 
				
			||||||
                val circularBitmapDrawable = RoundedBitmapDrawableFactory.create(
 | 
					 | 
				
			||||||
                    resources,
 | 
					 | 
				
			||||||
                    resource
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                circularBitmapDrawable.isCircular = true
 | 
					 | 
				
			||||||
                iv.setImageDrawable(circularBitmapDrawable)
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
fun getBitmapInputStream(bitmap:Bitmap,compressFormat: Bitmap.CompressFormat): InputStream {
 | 
					fun getBitmapInputStream(bitmap:Bitmap,compressFormat: Bitmap.CompressFormat): InputStream {
 | 
				
			||||||
    val byteArrayOutputStream = ByteArrayOutputStream()
 | 
					    val byteArrayOutputStream = ByteArrayOutputStream()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -39,7 +39,7 @@
 | 
				
			|||||||
            app:layout_constraintStart_toStartOf="parent"
 | 
					            app:layout_constraintStart_toStartOf="parent"
 | 
				
			||||||
            app:layout_constraintTop_toBottomOf="@+id/itemImage">
 | 
					            app:layout_constraintTop_toBottomOf="@+id/itemImage">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <ImageView
 | 
					            <bou.amine.apps.readerforselfossv2.android.utils.CircleImageView
 | 
				
			||||||
                android:id="@+id/sourceImage"
 | 
					                android:id="@+id/sourceImage"
 | 
				
			||||||
                android:layout_width="40dp"
 | 
					                android:layout_width="40dp"
 | 
				
			||||||
                android:layout_height="40dp"
 | 
					                android:layout_height="40dp"
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										26
									
								
								androidApp/src/main/res/layout/circle_image_view.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								androidApp/src/main/res/layout/circle_image_view.xml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					<?xml version="1.0" encoding="utf-8"?>
 | 
				
			||||||
 | 
					<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 | 
				
			||||||
 | 
					    xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
				
			||||||
 | 
					    android:layout_width="wrap_content"
 | 
				
			||||||
 | 
					    android:layout_height="wrap_content">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <com.google.android.material.imageview.ShapeableImageView
 | 
				
			||||||
 | 
					        android:id="@+id/circleImage"
 | 
				
			||||||
 | 
					        android:layout_width="match_parent"
 | 
				
			||||||
 | 
					        android:layout_height="match_parent"
 | 
				
			||||||
 | 
					        android:scaleType="centerCrop"
 | 
				
			||||||
 | 
					        app:shapeAppearanceOverlay="@style/circleImageView"
 | 
				
			||||||
 | 
					        app:srcCompat="@drawable/background_splash" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <TextView
 | 
				
			||||||
 | 
					        android:id="@+id/circleText"
 | 
				
			||||||
 | 
					        android:layout_width="match_parent"
 | 
				
			||||||
 | 
					        android:layout_height="match_parent"
 | 
				
			||||||
 | 
					        android:ellipsize="none"
 | 
				
			||||||
 | 
					        android:gravity="center"
 | 
				
			||||||
 | 
					        android:singleLine="true"
 | 
				
			||||||
 | 
					        android:textColor="@color/white"
 | 
				
			||||||
 | 
					        android:textIsSelectable="false"
 | 
				
			||||||
 | 
					        android:textSize="20sp"
 | 
				
			||||||
 | 
					        android:typeface="normal" />
 | 
				
			||||||
 | 
					</RelativeLayout>
 | 
				
			||||||
@@ -5,7 +5,7 @@
 | 
				
			|||||||
    android:layout_width="match_parent"
 | 
					    android:layout_width="match_parent"
 | 
				
			||||||
    android:layout_height="wrap_content">
 | 
					    android:layout_height="wrap_content">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <ImageView
 | 
					    <bou.amine.apps.readerforselfossv2.android.utils.CircleImageView
 | 
				
			||||||
        android:id="@+id/itemImage"
 | 
					        android:id="@+id/itemImage"
 | 
				
			||||||
        android:layout_width="46dp"
 | 
					        android:layout_width="46dp"
 | 
				
			||||||
        android:layout_height="46dp"
 | 
					        android:layout_height="46dp"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,7 +25,7 @@
 | 
				
			|||||||
        app:layout_constraintTop_toTopOf="parent"
 | 
					        app:layout_constraintTop_toTopOf="parent"
 | 
				
			||||||
        app:layout_constraintVertical_bias="0.0" />
 | 
					        app:layout_constraintVertical_bias="0.0" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <ImageView
 | 
					    <bou.amine.apps.readerforselfossv2.android.utils.CircleImageView
 | 
				
			||||||
        android:id="@+id/itemImage"
 | 
					        android:id="@+id/itemImage"
 | 
				
			||||||
        android:layout_width="36dp"
 | 
					        android:layout_width="36dp"
 | 
				
			||||||
        android:layout_height="36dp"
 | 
					        android:layout_height="36dp"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -32,4 +32,10 @@
 | 
				
			|||||||
        <item name="android:colorBackgroundCacheHint">@null</item>
 | 
					        <item name="android:colorBackgroundCacheHint">@null</item>
 | 
				
			||||||
        <item name="android:windowIsTranslucent">true</item>
 | 
					        <item name="android:windowIsTranslucent">true</item>
 | 
				
			||||||
    </style>
 | 
					    </style>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <style name="circleImageView" parent="">
 | 
				
			||||||
 | 
					        <item name="cornerFamily">rounded</item>
 | 
				
			||||||
 | 
					        <item name="cornerSize">50%</item>
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</resources>
 | 
					</resources>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -300,9 +300,10 @@ class RepositoryTest {
 | 
				
			|||||||
        every { appSettingsService.isItemCachingEnabled() } returns true
 | 
					        every { appSettingsService.isItemCachingEnabled() } returns true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        initializeRepository(MutableStateFlow(false))
 | 
					        initializeRepository(MutableStateFlow(false))
 | 
				
			||||||
        repository.setSourceFilter(SelfossModel.Source(
 | 
					        repository.setSourceFilter(SelfossModel.SourceDetail(
 | 
				
			||||||
            1,
 | 
					            1,
 | 
				
			||||||
            "Test",
 | 
					            "Test",
 | 
				
			||||||
 | 
					            null,
 | 
				
			||||||
            listOf("tags"),
 | 
					            listOf("tags"),
 | 
				
			||||||
            SPOUT,
 | 
					            SPOUT,
 | 
				
			||||||
            "",
 | 
					            "",
 | 
				
			||||||
@@ -609,30 +610,32 @@ class RepositoryTest {
 | 
				
			|||||||
    fun get_sources() {
 | 
					    fun get_sources() {
 | 
				
			||||||
        val (sources, sourcesDB) = prepareSources()
 | 
					        val (sources, sourcesDB) = prepareSources()
 | 
				
			||||||
        initializeRepository()
 | 
					        initializeRepository()
 | 
				
			||||||
        var testSources: List<SelfossModel.Source>?
 | 
					        var testSources: List<SelfossModel.Source>
 | 
				
			||||||
        runBlocking {
 | 
					        runBlocking {
 | 
				
			||||||
            testSources = repository.getSources()
 | 
					            testSources = repository.getSourcesDetails()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assertSame(sources, testSources)
 | 
					        assertEquals(sources, testSources)
 | 
				
			||||||
        assertNotEquals(sourcesDB.map { it.toView() }, testSources)
 | 
					        assertNotEquals(sourcesDB.map { it.toView() }, testSources)
 | 
				
			||||||
        coVerify(exactly = 1) { api.sources() }
 | 
					        coVerify(exactly = 1) { api.sourcesDetailed() }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun prepareSources(): Pair<ArrayList<SelfossModel.Source>, List<SOURCE>> {
 | 
					    private fun prepareSources(): Pair<ArrayList<SelfossModel.SourceDetail>, List<SOURCE>> {
 | 
				
			||||||
        val sources = arrayListOf(
 | 
					        val sources = arrayListOf(
 | 
				
			||||||
            SelfossModel.Source(
 | 
					            SelfossModel.SourceDetail(
 | 
				
			||||||
                1,
 | 
					                1,
 | 
				
			||||||
                "First source",
 | 
					                "First source",
 | 
				
			||||||
 | 
					                null,
 | 
				
			||||||
                listOf("Test", "second"),
 | 
					                listOf("Test", "second"),
 | 
				
			||||||
                SPOUT,
 | 
					                SPOUT,
 | 
				
			||||||
                "",
 | 
					                "",
 | 
				
			||||||
                IMAGE_URL_2,
 | 
					                IMAGE_URL_2,
 | 
				
			||||||
                SelfossModel.SourceParams("url")
 | 
					                SelfossModel.SourceParams("url")
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
            SelfossModel.Source(
 | 
					            SelfossModel.SourceDetail(
 | 
				
			||||||
                2,
 | 
					                2,
 | 
				
			||||||
                "Second source",
 | 
					                "Second source",
 | 
				
			||||||
 | 
					                null,
 | 
				
			||||||
                listOf("second"),
 | 
					                listOf("second"),
 | 
				
			||||||
                SPOUT,
 | 
					                SPOUT,
 | 
				
			||||||
                "",
 | 
					                "",
 | 
				
			||||||
@@ -661,7 +664,7 @@ class RepositoryTest {
 | 
				
			|||||||
            )
 | 
					            )
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        coEvery { api.sources() } returns StatusAndData(success = true, data = sources)
 | 
					        coEvery { api.sourcesDetailed() } returns StatusAndData(success = true, data = sources)
 | 
				
			||||||
        every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB
 | 
					        every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB
 | 
				
			||||||
        return Pair(sources, sourcesDB)
 | 
					        return Pair(sources, sourcesDB)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -675,13 +678,13 @@ class RepositoryTest {
 | 
				
			|||||||
        initializeRepository()
 | 
					        initializeRepository()
 | 
				
			||||||
        var testSources: List<SelfossModel.Source>?
 | 
					        var testSources: List<SelfossModel.Source>?
 | 
				
			||||||
        runBlocking {
 | 
					        runBlocking {
 | 
				
			||||||
            testSources = repository.getSources()
 | 
					            testSources = repository.getSourcesDetails()
 | 
				
			||||||
            // Sources will be fetched from the database on the second call, thus testSources != sources
 | 
					            // Sources will be fetched from the database on the second call, thus testSources != sources
 | 
				
			||||||
            testSources = repository.getSources()
 | 
					            testSources = repository.getSourcesDetails()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        coVerify(exactly = 1) { api.sources() }
 | 
					        coVerify(exactly = 1) { api.sourcesDetailed() }
 | 
				
			||||||
        assertNotSame(sources, testSources)
 | 
					        assertNotEquals(sources, testSources)
 | 
				
			||||||
        assertEquals(sourcesDB.map { it.toView() }, testSources)
 | 
					        assertEquals(sourcesDB.map { it.toView() }, testSources)
 | 
				
			||||||
        verify(atLeast = 1) { db.sourcesQueries.sources().executeAsList() }
 | 
					        verify(atLeast = 1) { db.sourcesQueries.sources().executeAsList() }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -693,13 +696,13 @@ class RepositoryTest {
 | 
				
			|||||||
        every { appSettingsService.isUpdateSourcesEnabled() } returns true
 | 
					        every { appSettingsService.isUpdateSourcesEnabled() } returns true
 | 
				
			||||||
        every { appSettingsService.isItemCachingEnabled() } returns false
 | 
					        every { appSettingsService.isItemCachingEnabled() } returns false
 | 
				
			||||||
        initializeRepository()
 | 
					        initializeRepository()
 | 
				
			||||||
        var testSources: List<SelfossModel.Source>?
 | 
					        var testSources: List<SelfossModel.Source>
 | 
				
			||||||
        runBlocking {
 | 
					        runBlocking {
 | 
				
			||||||
            testSources = repository.getSources()
 | 
					            testSources = repository.getSourcesDetails()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assertSame(sources, testSources)
 | 
					        assertEquals(sources, testSources)
 | 
				
			||||||
        coVerify(exactly = 1) { api.sources() }
 | 
					        coVerify(exactly = 1) { api.sourcesDetailed() }
 | 
				
			||||||
        verify(exactly = 0) { db.sourcesQueries }
 | 
					        verify(exactly = 0) { db.sourcesQueries }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -710,13 +713,13 @@ class RepositoryTest {
 | 
				
			|||||||
        every { appSettingsService.isUpdateSourcesEnabled() } returns false
 | 
					        every { appSettingsService.isUpdateSourcesEnabled() } returns false
 | 
				
			||||||
        every { appSettingsService.isItemCachingEnabled() } returns false
 | 
					        every { appSettingsService.isItemCachingEnabled() } returns false
 | 
				
			||||||
        initializeRepository()
 | 
					        initializeRepository()
 | 
				
			||||||
        var testSources: List<SelfossModel.Source>?
 | 
					        var testSources: List<SelfossModel.Source>
 | 
				
			||||||
        runBlocking {
 | 
					        runBlocking {
 | 
				
			||||||
            testSources = repository.getSources()
 | 
					            testSources = repository.getSourcesDetails()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assertSame(sources, testSources)
 | 
					        assertEquals(sources, testSources)
 | 
				
			||||||
        coVerify(exactly = 1) { api.sources() }
 | 
					        coVerify(exactly = 1) { api.sourcesDetailed() }
 | 
				
			||||||
        verify(atLeast = 1) { db.sourcesQueries }
 | 
					        verify(atLeast = 1) { db.sourcesQueries }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -724,13 +727,13 @@ class RepositoryTest {
 | 
				
			|||||||
    fun get_sources_without_connection() {
 | 
					    fun get_sources_without_connection() {
 | 
				
			||||||
        val (_, sourcesDB) = prepareSources()
 | 
					        val (_, sourcesDB) = prepareSources()
 | 
				
			||||||
        initializeRepository(MutableStateFlow(false))
 | 
					        initializeRepository(MutableStateFlow(false))
 | 
				
			||||||
        var testSources: List<SelfossModel.Source>?
 | 
					        var testSources: List<SelfossModel.Source>
 | 
				
			||||||
        runBlocking {
 | 
					        runBlocking {
 | 
				
			||||||
            testSources = repository.getSources()
 | 
					            testSources = repository.getSourcesDetails()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assertEquals(sourcesDB.map { it.toView() }, testSources)
 | 
					        assertEquals(sourcesDB.map { it.toView() }, testSources)
 | 
				
			||||||
        coVerify(exactly = 0) { api.sources() }
 | 
					        coVerify(exactly = 0) { api.sourcesDetailed() }
 | 
				
			||||||
        verify(atLeast = 1) { db.sourcesQueries.sources().executeAsList() }
 | 
					        verify(atLeast = 1) { db.sourcesQueries.sources().executeAsList() }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -741,13 +744,13 @@ class RepositoryTest {
 | 
				
			|||||||
        every { appSettingsService.isItemCachingEnabled() } returns false
 | 
					        every { appSettingsService.isItemCachingEnabled() } returns false
 | 
				
			||||||
        every { appSettingsService.isUpdateSourcesEnabled() } returns true
 | 
					        every { appSettingsService.isUpdateSourcesEnabled() } returns true
 | 
				
			||||||
        initializeRepository(MutableStateFlow(false))
 | 
					        initializeRepository(MutableStateFlow(false))
 | 
				
			||||||
        var testSources: List<SelfossModel.Source>?
 | 
					        var testSources: List<SelfossModel.Source>
 | 
				
			||||||
        runBlocking {
 | 
					        runBlocking {
 | 
				
			||||||
            testSources = repository.getSources()
 | 
					            testSources = repository.getSourcesDetails()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assertEquals(emptyList<SelfossModel.Source>(), testSources)
 | 
					        assertEquals(emptyList<SelfossModel.Source>(), testSources)
 | 
				
			||||||
        coVerify(exactly = 0) { api.sources() }
 | 
					        coVerify(exactly = 0) { api.sourcesDetailed() }
 | 
				
			||||||
        verify(exactly = 0) { db.sourcesQueries.sources().executeAsList() }
 | 
					        verify(exactly = 0) { db.sourcesQueries.sources().executeAsList() }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -758,13 +761,13 @@ class RepositoryTest {
 | 
				
			|||||||
        every { appSettingsService.isItemCachingEnabled() } returns true
 | 
					        every { appSettingsService.isItemCachingEnabled() } returns true
 | 
				
			||||||
        every { appSettingsService.isUpdateSourcesEnabled() } returns false
 | 
					        every { appSettingsService.isUpdateSourcesEnabled() } returns false
 | 
				
			||||||
        initializeRepository(MutableStateFlow(false))
 | 
					        initializeRepository(MutableStateFlow(false))
 | 
				
			||||||
        var testSources: List<SelfossModel.Source>?
 | 
					        var testSources: List<SelfossModel.Source>
 | 
				
			||||||
        runBlocking {
 | 
					        runBlocking {
 | 
				
			||||||
            testSources = repository.getSources()
 | 
					            testSources = repository.getSourcesDetails()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assertEquals(sourcesDB.map { it.toView() }, testSources)
 | 
					        assertEquals(sourcesDB.map { it.toView() }, testSources)
 | 
				
			||||||
        coVerify(exactly = 0) { api.sources() }
 | 
					        coVerify(exactly = 0) { api.sourcesDetailed() }
 | 
				
			||||||
        verify(atLeast = 1) { db.sourcesQueries.sources().executeAsList() }
 | 
					        verify(atLeast = 1) { db.sourcesQueries.sources().executeAsList() }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -775,13 +778,13 @@ class RepositoryTest {
 | 
				
			|||||||
        every { appSettingsService.isItemCachingEnabled() } returns false
 | 
					        every { appSettingsService.isItemCachingEnabled() } returns false
 | 
				
			||||||
        every { appSettingsService.isUpdateSourcesEnabled() } returns false
 | 
					        every { appSettingsService.isUpdateSourcesEnabled() } returns false
 | 
				
			||||||
        initializeRepository(MutableStateFlow(false))
 | 
					        initializeRepository(MutableStateFlow(false))
 | 
				
			||||||
        var testSources: List<SelfossModel.Source>?
 | 
					        var testSources: List<SelfossModel.Source>
 | 
				
			||||||
        runBlocking {
 | 
					        runBlocking {
 | 
				
			||||||
            testSources = repository.getSources()
 | 
					            testSources = repository.getSourcesDetails()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        assertEquals(sourcesDB.map { it.toView() }, testSources)
 | 
					        assertEquals(sourcesDB.map { it.toView() }, testSources)
 | 
				
			||||||
        coVerify(exactly = 0) { api.sources() }
 | 
					        coVerify(exactly = 0) { api.sourcesDetailed() }
 | 
				
			||||||
        verify(atLeast = 1) { db.sourcesQueries.sources().executeAsList() }
 | 
					        verify(atLeast = 1) { db.sourcesQueries.sources().executeAsList() }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1102,9 +1105,10 @@ class RepositoryTest {
 | 
				
			|||||||
    private fun prepareSearch() {
 | 
					    private fun prepareSearch() {
 | 
				
			||||||
        repository.setTagFilter(SelfossModel.Tag("Tag", "read", 0))
 | 
					        repository.setTagFilter(SelfossModel.Tag("Tag", "read", 0))
 | 
				
			||||||
        repository.setSourceFilter(
 | 
					        repository.setSourceFilter(
 | 
				
			||||||
            SelfossModel.Source(
 | 
					            SelfossModel.SourceDetail(
 | 
				
			||||||
                1,
 | 
					                1,
 | 
				
			||||||
                "First source",
 | 
					                "First source",
 | 
				
			||||||
 | 
					                5,
 | 
				
			||||||
                listOf("Test", "second"),
 | 
					                listOf("Test", "second"),
 | 
				
			||||||
                SPOUT,
 | 
					                SPOUT,
 | 
				
			||||||
                "",
 | 
					                "",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,8 +24,8 @@ class SelfossModel {
 | 
				
			|||||||
    @Serializable
 | 
					    @Serializable
 | 
				
			||||||
    class Stats(
 | 
					    class Stats(
 | 
				
			||||||
        val total: Int,
 | 
					        val total: Int,
 | 
				
			||||||
        val unread: Int,
 | 
					        val unread: Int?,
 | 
				
			||||||
        val starred: Int
 | 
					        val starred: Int?
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Serializable
 | 
					    @Serializable
 | 
				
			||||||
@@ -63,20 +63,39 @@ class SelfossModel {
 | 
				
			|||||||
        fun isPublicModeEnabled() = publicMode ?: false
 | 
					        fun isPublicModeEnabled() = publicMode ?: false
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    interface Source {
 | 
				
			||||||
 | 
					        val id: Int
 | 
				
			||||||
 | 
					        var title: String
 | 
				
			||||||
 | 
					        var unread: Int?
 | 
				
			||||||
 | 
					        var error: String?
 | 
				
			||||||
 | 
					        var icon: String?
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Serializable
 | 
					    @Serializable
 | 
				
			||||||
    data class Source(
 | 
					    data class SourceStats(
 | 
				
			||||||
        val id: Int,
 | 
					        override val id: Int,
 | 
				
			||||||
        val title: String,
 | 
					        override var title: String,
 | 
				
			||||||
 | 
					        override var unread: Int?,
 | 
				
			||||||
 | 
					        override var error: String? = null,
 | 
				
			||||||
 | 
					        override var icon: String? = null
 | 
				
			||||||
 | 
					        ) : Source
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Serializable
 | 
				
			||||||
 | 
					    data class SourceDetail(
 | 
				
			||||||
 | 
					        override val id: Int,
 | 
				
			||||||
 | 
					        override var title: String,
 | 
				
			||||||
 | 
					        override var unread: Int? = null,
 | 
				
			||||||
        @Serializable(with = TagsListSerializer::class)
 | 
					        @Serializable(with = TagsListSerializer::class)
 | 
				
			||||||
        val tags: List<String>,
 | 
					        var tags: List<String>?,
 | 
				
			||||||
        val spout: String,
 | 
					        var spout: String?,
 | 
				
			||||||
        val error: String,
 | 
					        override var error: String?,
 | 
				
			||||||
        val icon: String?,
 | 
					        override var icon: String?,
 | 
				
			||||||
        val params: SourceParams?
 | 
					        var params: SourceParams?
 | 
				
			||||||
    )
 | 
					    ) : Source
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Serializable
 | 
					    @Serializable
 | 
				
			||||||
    data class SourceParams(
 | 
					    data class SourceParams(
 | 
				
			||||||
        val url: String?
 | 
					        val url: String? = null
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    @Serializable
 | 
					    @Serializable
 | 
				
			||||||
    data class Item(
 | 
					    data class Item(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -44,11 +44,11 @@ class Repository(
 | 
				
			|||||||
    private val _badgeStarred = MutableStateFlow(0)
 | 
					    private val _badgeStarred = MutableStateFlow(0)
 | 
				
			||||||
    val badgeStarred = _badgeStarred.asStateFlow()
 | 
					    val badgeStarred = _badgeStarred.asStateFlow()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private var fetchedSources = false
 | 
					 | 
				
			||||||
    private var fetchedTags = false
 | 
					    private var fetchedTags = false
 | 
				
			||||||
 | 
					    private var fetchedSources = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private var _readerItems = ArrayList<SelfossModel.Item>()
 | 
					    private var _readerItems = ArrayList<SelfossModel.Item>()
 | 
				
			||||||
    private var _selectedSource: SelfossModel.Source? = null
 | 
					    private var _selectedSource: SelfossModel.SourceDetail? = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    suspend fun getNewerItems(): ArrayList<SelfossModel.Item> {
 | 
					    suspend fun getNewerItems(): ArrayList<SelfossModel.Item> {
 | 
				
			||||||
        var fetchedItems: StatusAndData<List<SelfossModel.Item>> = StatusAndData.error()
 | 
					        var fetchedItems: StatusAndData<List<SelfossModel.Item>> = StatusAndData.error()
 | 
				
			||||||
@@ -132,9 +132,9 @@ class Repository(
 | 
				
			|||||||
        if (isNetworkAvailable()) {
 | 
					        if (isNetworkAvailable()) {
 | 
				
			||||||
            val response = api.stats()
 | 
					            val response = api.stats()
 | 
				
			||||||
            if (response.success && response.data != null) {
 | 
					            if (response.success && response.data != null) {
 | 
				
			||||||
                _badgeUnread.value = response.data.unread
 | 
					                _badgeUnread.value = response.data.unread ?: 0
 | 
				
			||||||
                _badgeAll.value = response.data.total
 | 
					                _badgeAll.value = response.data.total
 | 
				
			||||||
                _badgeStarred.value = response.data.starred
 | 
					                _badgeStarred.value = response.data.starred ?: 0
 | 
				
			||||||
                success = true
 | 
					                success = true
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        } else if (appSettingsService.isItemCachingEnabled()) {
 | 
					        } else if (appSettingsService.isItemCachingEnabled()) {
 | 
				
			||||||
@@ -180,23 +180,46 @@ class Repository(
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    suspend fun getSources(): ArrayList<SelfossModel.Source> {
 | 
					    suspend fun getSourcesDetailsOrStats(): ArrayList<SelfossModel.Source> {
 | 
				
			||||||
 | 
					        var sources = ArrayList<SelfossModel.Source>()
 | 
				
			||||||
        val isDatabaseEnabled =
 | 
					        val isDatabaseEnabled =
 | 
				
			||||||
            appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled()
 | 
					            appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled()
 | 
				
			||||||
        return if (isNetworkAvailable() && !fetchedSources) {
 | 
					        val shouldFetch = if (!appSettingsService.isUpdateSourcesEnabled()) !fetchedSources else true
 | 
				
			||||||
            val apiSources = api.sources()
 | 
					        if (shouldFetch && isNetworkAvailable()) {
 | 
				
			||||||
            if (apiSources.success && apiSources.data != null && isDatabaseEnabled) {
 | 
					            if (appSettingsService.getPublicAccess()) {
 | 
				
			||||||
                resetDBSourcesWithData(apiSources.data)
 | 
					                val apiSources = api.sourcesStats()
 | 
				
			||||||
                if (!appSettingsService.isUpdateSourcesEnabled()) {
 | 
					                if (apiSources.success && apiSources.data != null) {
 | 
				
			||||||
                    fetchedSources = true
 | 
					                    fetchedSources = true
 | 
				
			||||||
 | 
					                    sources = apiSources.data as ArrayList<SelfossModel.Source>
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                sources = getSourcesDetails() as ArrayList<SelfossModel.Source>
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else if (isDatabaseEnabled) {
 | 
				
			||||||
 | 
					            sources = getDBSources().map { it.toView() } as ArrayList<SelfossModel.Source>
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return sources
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    suspend fun getSourcesDetails(): ArrayList<SelfossModel.SourceDetail> {
 | 
				
			||||||
 | 
					        var sources = ArrayList<SelfossModel.SourceDetail>()
 | 
				
			||||||
 | 
					        val isDatabaseEnabled =
 | 
				
			||||||
 | 
					            appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled()
 | 
				
			||||||
 | 
					        val shouldFetch = if (!appSettingsService.isUpdateSourcesEnabled()) !fetchedSources else true
 | 
				
			||||||
 | 
					        if (shouldFetch && isNetworkAvailable()) {
 | 
				
			||||||
 | 
					            val apiSources = api.sourcesDetailed()
 | 
				
			||||||
 | 
					            if (apiSources.success && apiSources.data != null) {
 | 
				
			||||||
 | 
					                fetchedSources = true
 | 
				
			||||||
 | 
					                sources = apiSources.data
 | 
				
			||||||
 | 
					                if (isDatabaseEnabled) {
 | 
				
			||||||
 | 
					                    resetDBSourcesWithData(sources)
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            apiSources.data ?: ArrayList()
 | 
					 | 
				
			||||||
        } else if (isDatabaseEnabled) {
 | 
					        } else if (isDatabaseEnabled) {
 | 
				
			||||||
            ArrayList(getDBSources().map { it.toView() })
 | 
					            sources = getDBSources().map { it.toView() } as ArrayList<SelfossModel.SourceDetail>
 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            ArrayList()
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        return sources
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    suspend fun markAsRead(item: SelfossModel.Item): Boolean {
 | 
					    suspend fun markAsRead(item: SelfossModel.Item): Boolean {
 | 
				
			||||||
@@ -482,7 +505,7 @@ class Repository(
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun resetDBSourcesWithData(sources: List<SelfossModel.Source>) {
 | 
					    private fun resetDBSourcesWithData(sources: List<SelfossModel.SourceDetail>) {
 | 
				
			||||||
        db.sourcesQueries.deleteAllSources()
 | 
					        db.sourcesQueries.deleteAllSources()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        db.sourcesQueries.transaction {
 | 
					        db.sourcesQueries.transaction {
 | 
				
			||||||
@@ -592,7 +615,7 @@ class Repository(
 | 
				
			|||||||
        ReaderForSelfossDB.Schema.migrate(driverFactory.createDriver(), 0, 1)
 | 
					        ReaderForSelfossDB.Schema.migrate(driverFactory.createDriver(), 0, 1)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun setSelectedSource(source: SelfossModel.Source) {
 | 
					    fun setSelectedSource(source: SelfossModel.SourceDetail) {
 | 
				
			||||||
        _selectedSource = source
 | 
					        _selectedSource = source
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -600,7 +623,7 @@ class Repository(
 | 
				
			|||||||
        _selectedSource = null
 | 
					        _selectedSource = null
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun getSelectedSource(): SelfossModel.Source? {
 | 
					    fun getSelectedSource(): SelfossModel.SourceDetail? {
 | 
				
			||||||
        return _selectedSource
 | 
					        return _selectedSource
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -183,7 +183,15 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    suspend fun sources(): StatusAndData<ArrayList<SelfossModel.Source>> =
 | 
					    suspend fun sourcesStats(): StatusAndData<ArrayList<SelfossModel.SourceStats>> =
 | 
				
			||||||
 | 
					        bodyOrFailure(client.tryToGet(url("/sources/stats")) {
 | 
				
			||||||
 | 
					            if (!shouldHavePostLogin()) {
 | 
				
			||||||
 | 
					                parameter("username", appSettingsService.getUserName())
 | 
				
			||||||
 | 
					                parameter("password", appSettingsService.getPassword())
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    suspend fun sourcesDetailed(): StatusAndData<ArrayList<SelfossModel.SourceDetail>> =
 | 
				
			||||||
        bodyOrFailure(client.tryToGet(url("/sources/list")) {
 | 
					        bodyOrFailure(client.tryToGet(url("/sources/list")) {
 | 
				
			||||||
            if (!shouldHavePostLogin()) {
 | 
					            if (!shouldHavePostLogin()) {
 | 
				
			||||||
                parameter("username", appSettingsService.getUserName())
 | 
					                parameter("username", appSettingsService.getUserName())
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,24 +12,25 @@ fun TAG.toView(): SelfossModel.Tag =
 | 
				
			|||||||
        this.unread.toInt()
 | 
					        this.unread.toInt()
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fun SOURCE.toView(): SelfossModel.Source =
 | 
					fun SOURCE.toView(): SelfossModel.SourceDetail =
 | 
				
			||||||
    SelfossModel.Source(
 | 
					    SelfossModel.SourceDetail(
 | 
				
			||||||
        this.id.toInt(),
 | 
					        this.id.toInt(),
 | 
				
			||||||
        this.title,
 | 
					        this.title,
 | 
				
			||||||
        this.tags.split(","),
 | 
					        null,
 | 
				
			||||||
 | 
					        this.tags?.split(","),
 | 
				
			||||||
        this.spout,
 | 
					        this.spout,
 | 
				
			||||||
        this.error,
 | 
					        this.error,
 | 
				
			||||||
        this.icon,
 | 
					        this.icon,
 | 
				
			||||||
        SelfossModel.SourceParams(this.url)
 | 
					        if (this.url != null) SelfossModel.SourceParams(this.url) else null
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fun SelfossModel.Source.toEntity(): SOURCE =
 | 
					fun SelfossModel.SourceDetail.toEntity(): SOURCE =
 | 
				
			||||||
    SOURCE(
 | 
					    SOURCE(
 | 
				
			||||||
        this.id.toString(),
 | 
					        this.id.toString(),
 | 
				
			||||||
        this.title.getHtmlDecoded(),
 | 
					        this.title.getHtmlDecoded(),
 | 
				
			||||||
        this.tags.joinToString(","),
 | 
					        this.tags?.joinToString(",").orEmpty(),
 | 
				
			||||||
        this.spout,
 | 
					        this.spout.orEmpty(),
 | 
				
			||||||
        this.error,
 | 
					        this.error.orEmpty(),
 | 
				
			||||||
        this.icon.orEmpty(),
 | 
					        this.icon.orEmpty(),
 | 
				
			||||||
        this.params?.url
 | 
					        this.params?.url
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user