Compare commits
	
		
			20 Commits
		
	
	
		
			v123030751
			...
			7f96798f13
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					7f96798f13 | ||
| 
						 | 
					6e5704a45b | ||
| 
						 | 
					495591159f | ||
| 
						 | 
					718fe7c5ee | ||
| 
						 | 
					ecd23213f9 | ||
| 
						 | 
					e6baed8cb4 | ||
| 
						 | 
					c87abec0b9 | ||
| 
						 | 
					0aba41d8bf | ||
| 
						 | 
					2a2d1047b4 | ||
| 
						 | 
					66ef1ccf32 | ||
| 
						 | 
					677ede5bc7 | ||
| 
						 | 
					996a7ed22c | ||
| 
						 | 
					85208c4e5a | ||
| 
						 | 
					5cfec50cba | ||
| 76ad71e1dc | |||
| 0277fb507c | |||
| 8d7d3174aa | |||
| 
						 | 
					00eb3333fe | ||
| 
						 | 
					629ca01d99 | ||
| 
						 | 
					c2d8681ce8 | 
							
								
								
									
										21
									
								
								.drone.yml
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								.drone.yml
									
									
									
									
									
								
							@@ -137,6 +137,27 @@ steps:
 | 
			
		||||
        from_secret: giteaAPI
 | 
			
		||||
      base_url: https://gitea.amine-louveau.fr
 | 
			
		||||
      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:
 | 
			
		||||
  event:
 | 
			
		||||
    - tag
 | 
			
		||||
							
								
								
									
										54
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										54
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -1,3 +1,57 @@
 | 
			
		||||
**v123051331**
 | 
			
		||||
 | 
			
		||||
- fix: illegal input.
 | 
			
		||||
- Changelog for v123051321 [CI SKIP]
 | 
			
		||||
 | 
			
		||||
--------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
**v123051321**
 | 
			
		||||
 | 
			
		||||
- debug: Debug null context.
 | 
			
		||||
- Changelog for v123051301 [CI SKIP]
 | 
			
		||||
 | 
			
		||||
--------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
**v123051301**
 | 
			
		||||
 | 
			
		||||
- feat: Basic auth from url. Fixes #142 (#143)
 | 
			
		||||
- debug: Debug index out of bound exception.
 | 
			
		||||
- Changelog for v123051211 [CI SKIP]
 | 
			
		||||
 | 
			
		||||
--------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
**v123051211**
 | 
			
		||||
 | 
			
		||||
- fix: Sometimes url isn't even defined.
 | 
			
		||||
- Changelog for v123041021 [CI SKIP]
 | 
			
		||||
 | 
			
		||||
--------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
**v123041021**
 | 
			
		||||
 | 
			
		||||
- fix: 'Enable Core Library Desugaring to support older Android versions' (#138) from davidoskky/ReaderForSelfoss-multiplatform:desugaring into master
 | 
			
		||||
- Enable Core Library Desugaring to support older Android versions
 | 
			
		||||
- Changelog for v123030851 [CI SKIP]
 | 
			
		||||
 | 
			
		||||
--------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
**v123030851**
 | 
			
		||||
 | 
			
		||||
- chore: replace textDrawable library (#136)
 | 
			
		||||
- refactor: Remove slow login check. Closes #135.
 | 
			
		||||
- ci: send the mapping file after a release.
 | 
			
		||||
- Changelog for v123030751 [CI SKIP]
 | 
			
		||||
 | 
			
		||||
--------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
**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.
 | 
			
		||||
 
 | 
			
		||||
@@ -56,6 +56,7 @@ fun versionNameFromGit(): String {
 | 
			
		||||
 | 
			
		||||
android {
 | 
			
		||||
    compileOptions {
 | 
			
		||||
        isCoreLibraryDesugaringEnabled = true
 | 
			
		||||
        // Flag to enable support for the new language APIs
 | 
			
		||||
        sourceCompatibility = JavaVersion.VERSION_11
 | 
			
		||||
        targetCompatibility = JavaVersion.VERSION_11
 | 
			
		||||
@@ -112,6 +113,8 @@ android {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.3")
 | 
			
		||||
 | 
			
		||||
    implementation(project(":shared"))
 | 
			
		||||
    implementation("com.google.android.material:material:1.5.0")
 | 
			
		||||
    implementation("androidx.appcompat:appcompat:1.4.1")
 | 
			
		||||
@@ -142,7 +145,6 @@ dependencies {
 | 
			
		||||
 | 
			
		||||
    // Material-ish things
 | 
			
		||||
    implementation("com.ashokvarma.android:bottom-navigation-bar:2.2.0")
 | 
			
		||||
    implementation("com.amulyakhare:com.amulyakhare.textdrawable:1.0.1")
 | 
			
		||||
 | 
			
		||||
    // glide
 | 
			
		||||
    kapt("com.github.bumptech.glide:compiler:4.15.0")
 | 
			
		||||
 
 | 
			
		||||
@@ -57,29 +57,7 @@ class LoginActivity : AppCompatActivity(), DIAware {
 | 
			
		||||
 | 
			
		||||
        if (appSettingsService.getBaseUrl().isNotEmpty()) {
 | 
			
		||||
            showProgress(true)
 | 
			
		||||
            // This should be reverted when "old" users connected with a non-selfoss rss
 | 
			
		||||
            // 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()
 | 
			
		||||
 
 | 
			
		||||
@@ -71,7 +71,11 @@ class ReaderActivity : AppCompatActivity(), DIAware {
 | 
			
		||||
            finish()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            readItem(allItems[currentItem])
 | 
			
		||||
        } catch (e: IndexOutOfBoundsException) {
 | 
			
		||||
            finish()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        binding.pager.adapter = ScreenSlidePagerAdapter(this)
 | 
			
		||||
        binding.pager.setCurrentItem(currentItem, false)
 | 
			
		||||
 
 | 
			
		||||
@@ -9,10 +9,9 @@ import android.widget.ImageView.ScaleType
 | 
			
		||||
import androidx.recyclerview.widget.RecyclerView
 | 
			
		||||
import bou.amine.apps.readerforselfossv2.android.R
 | 
			
		||||
import bou.amine.apps.readerforselfossv2.android.databinding.CardItemBinding
 | 
			
		||||
import bou.amine.apps.readerforselfossv2.android.model.toTextDrawableString
 | 
			
		||||
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.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.openItemUrl
 | 
			
		||||
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.getIcon
 | 
			
		||||
import bou.amine.apps.readerforselfossv2.utils.getThumbnail
 | 
			
		||||
import com.amulyakhare.textdrawable.TextDrawable
 | 
			
		||||
import com.amulyakhare.textdrawable.util.ColorGenerator
 | 
			
		||||
import com.bumptech.glide.Glide
 | 
			
		||||
import kotlinx.coroutines.CoroutineScope
 | 
			
		||||
import kotlinx.coroutines.Dispatchers
 | 
			
		||||
@@ -38,7 +35,6 @@ class ItemCardAdapter(
 | 
			
		||||
    override val updateItems: (ArrayList<SelfossModel.Item>) -> Unit
 | 
			
		||||
) : ItemsAdapter<ItemCardAdapter.ViewHolder>() {
 | 
			
		||||
    private val c: Context = app.baseContext
 | 
			
		||||
    private val generator: ColorGenerator = ColorGenerator.MATERIAL
 | 
			
		||||
    private val imageMaxHeight: Int =
 | 
			
		||||
        c.resources.getDimension(R.dimen.card_image_max_height).toInt()
 | 
			
		||||
 | 
			
		||||
@@ -83,16 +79,9 @@ class ItemCardAdapter(
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (itm.getIcon(repository.baseUrl).isEmpty()) {
 | 
			
		||||
                val color = generator.getColor(itm.title.getHtmlDecoded())
 | 
			
		||||
 | 
			
		||||
                val drawable =
 | 
			
		||||
                    TextDrawable
 | 
			
		||||
                        .builder()
 | 
			
		||||
                        .round()
 | 
			
		||||
                        .build(itm.title.getHtmlDecoded().toTextDrawableString(), color)
 | 
			
		||||
                binding.sourceImage.setImageDrawable(drawable)
 | 
			
		||||
                binding.sourceImage.setBackgroundAndText(itm.sourcetitle.getHtmlDecoded())
 | 
			
		||||
            } 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 bou.amine.apps.readerforselfossv2.android.R
 | 
			
		||||
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.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.openItemUrl
 | 
			
		||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
 | 
			
		||||
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.getIcon
 | 
			
		||||
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.android.closestDI
 | 
			
		||||
import org.kodein.di.instance
 | 
			
		||||
@@ -29,7 +25,6 @@ class ItemListAdapter(
 | 
			
		||||
    override var items: ArrayList<SelfossModel.Item>,
 | 
			
		||||
    override val updateItems: (ArrayList<SelfossModel.Item>) -> Unit
 | 
			
		||||
) : ItemsAdapter<ItemListAdapter.ViewHolder>() {
 | 
			
		||||
    private val generator: ColorGenerator = ColorGenerator.MATERIAL
 | 
			
		||||
    private val c: Context = app.baseContext
 | 
			
		||||
 | 
			
		||||
    override val di: DI by closestDI(app)
 | 
			
		||||
@@ -56,20 +51,12 @@ class ItemListAdapter(
 | 
			
		||||
            if (itm.getThumbnail(repository.baseUrl).isEmpty()) {
 | 
			
		||||
 | 
			
		||||
                if (itm.getIcon(repository.baseUrl).isEmpty()) {
 | 
			
		||||
                    val color = generator.getColor(itm.title.getHtmlDecoded())
 | 
			
		||||
 | 
			
		||||
                    val drawable =
 | 
			
		||||
                            TextDrawable
 | 
			
		||||
                                    .builder()
 | 
			
		||||
                                    .round()
 | 
			
		||||
                                    .build(itm.title.getHtmlDecoded().toTextDrawableString(), color)
 | 
			
		||||
 | 
			
		||||
                    binding.itemImage.setImageDrawable(drawable)
 | 
			
		||||
                    binding.itemImage.setBackgroundAndText(itm.sourcetitle.getHtmlDecoded())
 | 
			
		||||
                } else {
 | 
			
		||||
                    c.circularBitmapDrawable(itm.getIcon(repository.baseUrl), binding.itemImage)
 | 
			
		||||
                    c.circularDrawable(itm.getIcon(repository.baseUrl), binding.itemImage)
 | 
			
		||||
                }
 | 
			
		||||
            } 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 androidx.constraintlayout.widget.ConstraintLayout
 | 
			
		||||
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.UpsertSourceActivity
 | 
			
		||||
import bou.amine.apps.readerforselfossv2.android.databinding.SourceListItemBinding
 | 
			
		||||
import bou.amine.apps.readerforselfossv2.android.model.toTextDrawableString
 | 
			
		||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularBitmapDrawable
 | 
			
		||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularDrawable
 | 
			
		||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
 | 
			
		||||
import bou.amine.apps.readerforselfossv2.repository.Repository
 | 
			
		||||
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
 | 
			
		||||
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.Dispatchers
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
@@ -34,7 +31,6 @@ class SourcesListAdapter(
 | 
			
		||||
    private val items: ArrayList<SelfossModel.SourceDetail>
 | 
			
		||||
) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>(), DIAware {
 | 
			
		||||
    private val c: Context = app.baseContext
 | 
			
		||||
    private val generator: ColorGenerator = ColorGenerator.MATERIAL
 | 
			
		||||
    private lateinit var binding: SourceListItemBinding
 | 
			
		||||
 | 
			
		||||
    override val di: DI by closestDI(app)
 | 
			
		||||
@@ -49,16 +45,9 @@ class SourcesListAdapter(
 | 
			
		||||
        val itm = items[position]
 | 
			
		||||
 | 
			
		||||
        if (itm.getIcon(repository.baseUrl).isEmpty()) {
 | 
			
		||||
            val color = generator.getColor(itm.title.getHtmlDecoded())
 | 
			
		||||
 | 
			
		||||
            val drawable =
 | 
			
		||||
                TextDrawable
 | 
			
		||||
                    .builder()
 | 
			
		||||
                    .round()
 | 
			
		||||
                    .build(itm.title.getHtmlDecoded().toTextDrawableString(), color)
 | 
			
		||||
            binding.itemImage.setImageDrawable(drawable)
 | 
			
		||||
            binding.itemImage.setBackgroundAndText(itm.title.getHtmlDecoded())
 | 
			
		||||
        } else {
 | 
			
		||||
            c.circularBitmapDrawable(itm.getIcon(repository.baseUrl), binding.itemImage)
 | 
			
		||||
            c.circularDrawable(itm.getIcon(repository.baseUrl), binding.itemImage)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!itm.error.isNullOrBlank()) {
 | 
			
		||||
 
 | 
			
		||||
@@ -31,7 +31,6 @@ import bou.amine.apps.readerforselfossv2.android.utils.openInBrowserAsNewTask
 | 
			
		||||
import bou.amine.apps.readerforselfossv2.android.utils.shareLink
 | 
			
		||||
import bou.amine.apps.readerforselfossv2.model.MercuryModel
 | 
			
		||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
 | 
			
		||||
import bou.amine.apps.readerforselfossv2.model.StatusAndData
 | 
			
		||||
import bou.amine.apps.readerforselfossv2.repository.Repository
 | 
			
		||||
import bou.amine.apps.readerforselfossv2.rest.MercuryApi
 | 
			
		||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
 | 
			
		||||
@@ -260,21 +259,8 @@ class ArticleFragment : Fragment(), DIAware {
 | 
			
		||||
        CoroutineScope(Dispatchers.Main).launch {
 | 
			
		||||
            try {
 | 
			
		||||
                val response = mercuryApi.query(url)
 | 
			
		||||
                if (response.success && response.data != null && !response.data?.content.isNullOrEmpty()) {
 | 
			
		||||
                    binding.titleView.text = response.data!!.title.orEmpty()
 | 
			
		||||
                    if (typeface != null) {
 | 
			
		||||
                        binding.titleView.typeface = typeface
 | 
			
		||||
                    }
 | 
			
		||||
                    URL(response.data!!.url)
 | 
			
		||||
                    url = response.data!!.url
 | 
			
		||||
 | 
			
		||||
                    contentText = response.data!!.content.orEmpty()
 | 
			
		||||
                    htmlToWebview()
 | 
			
		||||
 | 
			
		||||
                    handleLeadImage(response)
 | 
			
		||||
 | 
			
		||||
                    binding.nestedScrollView.scrollTo(0, 0)
 | 
			
		||||
                    binding.progressBar.visibility = View.GONE
 | 
			
		||||
                if (response.success && response.data != null) {
 | 
			
		||||
                    handleMercuryData(response.data!!)
 | 
			
		||||
                } else {
 | 
			
		||||
                    openInBrowserAfterFailing()
 | 
			
		||||
                }
 | 
			
		||||
@@ -287,14 +273,35 @@ class ArticleFragment : Fragment(), DIAware {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun handleLeadImage(response: StatusAndData<MercuryModel.ParsedContent>) {
 | 
			
		||||
        if (!response.data?.lead_image_url.isNullOrEmpty() && context != null) {
 | 
			
		||||
    private fun handleMercuryData(data: MercuryModel.ParsedContent) {
 | 
			
		||||
        if (data.error == true || data.failed == true) {
 | 
			
		||||
            openInBrowserAfterFailing()
 | 
			
		||||
        } else {
 | 
			
		||||
            binding.titleView.text = data.title.orEmpty()
 | 
			
		||||
            if (typeface != null) {
 | 
			
		||||
                binding.titleView.typeface = typeface
 | 
			
		||||
            }
 | 
			
		||||
            URL(data.url)
 | 
			
		||||
            url = data.url!!
 | 
			
		||||
 | 
			
		||||
            contentText = data.content.orEmpty()
 | 
			
		||||
            htmlToWebview()
 | 
			
		||||
 | 
			
		||||
            handleLeadImage(data?.lead_image_url)
 | 
			
		||||
 | 
			
		||||
            binding.nestedScrollView.scrollTo(0, 0)
 | 
			
		||||
            binding.progressBar.visibility = View.GONE
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun handleLeadImage(lead_image_url: String?) {
 | 
			
		||||
        if (!lead_image_url.isNullOrEmpty() && context != null) {
 | 
			
		||||
            binding.imageView.visibility = View.VISIBLE
 | 
			
		||||
            Glide
 | 
			
		||||
                .with(requireContext())
 | 
			
		||||
                .asBitmap()
 | 
			
		||||
                .load(
 | 
			
		||||
                    response.data!!.lead_image_url.orEmpty()
 | 
			
		||||
                    lead_image_url
 | 
			
		||||
                )
 | 
			
		||||
                .apply(RequestOptions.fitCenterTransform())
 | 
			
		||||
                .into(binding.imageView)
 | 
			
		||||
@@ -523,7 +530,12 @@ class ArticleFragment : Fragment(), DIAware {
 | 
			
		||||
 | 
			
		||||
    private fun openInBrowserAfterFailing() {
 | 
			
		||||
        binding.progressBar.visibility = View.GONE
 | 
			
		||||
        requireActivity().openInBrowserAsNewTask(this@ArticleFragment.item)
 | 
			
		||||
        if (context != null) {
 | 
			
		||||
            requireContext().openInBrowserAsNewTask(this@ArticleFragment.item)
 | 
			
		||||
        } else {
 | 
			
		||||
            Exception("openInBrowserAfterFailing context is null").sendSilentlyWithAcraWithName("openInBrowserAfterFailing > $context")
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
@@ -541,8 +553,8 @@ class ArticleFragment : Fragment(), DIAware {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun performClick(): Boolean {
 | 
			
		||||
        if (binding.webcontent.hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE ||
 | 
			
		||||
            binding.webcontent.hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE
 | 
			
		||||
        if (allImages != null && (binding.webcontent.hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE ||
 | 
			
		||||
            binding.webcontent.hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE)
 | 
			
		||||
        ) {
 | 
			
		||||
 | 
			
		||||
            val position: Int = allImages.indexOf(binding.webcontent.hitTestResult.extra)
 | 
			
		||||
 
 | 
			
		||||
@@ -149,6 +149,7 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
 | 
			
		||||
            c.ellipsize = TextUtils.TruncateAt.END
 | 
			
		||||
            c.text = tag.tag
 | 
			
		||||
 | 
			
		||||
            if (tag.color.isNotEmpty()) {
 | 
			
		||||
                try {
 | 
			
		||||
                    val gd = GradientDrawable()
 | 
			
		||||
                    val gdColor = try {
 | 
			
		||||
@@ -165,6 +166,7 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
 | 
			
		||||
                } catch (e: Exception) {
 | 
			
		||||
                    e.sendSilentlyWithAcraWithName("tags > GradientDrawable")
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            c.setOnCloseIconClickListener {
 | 
			
		||||
                (it as Chip).isCloseIconVisible = false
 | 
			
		||||
 
 | 
			
		||||
@@ -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.graphics.Bitmap
 | 
			
		||||
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.request.RequestOptions
 | 
			
		||||
import com.bumptech.glide.request.target.BitmapImageViewTarget
 | 
			
		||||
import java.io.ByteArrayInputStream
 | 
			
		||||
import java.io.ByteArrayOutputStream
 | 
			
		||||
import java.io.InputStream
 | 
			
		||||
@@ -18,21 +17,13 @@ fun Context.bitmapCenterCrop(url: String, iv: ImageView) =
 | 
			
		||||
        .apply(RequestOptions.centerCropTransform())
 | 
			
		||||
        .into(iv)
 | 
			
		||||
 | 
			
		||||
fun Context.circularBitmapDrawable(url: String, iv: ImageView) =
 | 
			
		||||
fun Context.circularDrawable(url: String, view: CircleImageView) {
 | 
			
		||||
    view.textView.text =""
 | 
			
		||||
 | 
			
		||||
    Glide.with(this)
 | 
			
		||||
        .asBitmap()
 | 
			
		||||
        .load(url)
 | 
			
		||||
        .apply(RequestOptions.centerCropTransform())
 | 
			
		||||
        .into(object : BitmapImageViewTarget(iv) {
 | 
			
		||||
            override fun setResource(resource: Bitmap?) {
 | 
			
		||||
                val circularBitmapDrawable = RoundedBitmapDrawableFactory.create(
 | 
			
		||||
                    resources,
 | 
			
		||||
                    resource
 | 
			
		||||
                )
 | 
			
		||||
                circularBitmapDrawable.isCircular = true
 | 
			
		||||
                iv.setImageDrawable(circularBitmapDrawable)
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
        .into(view.imageView)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun getBitmapInputStream(bitmap:Bitmap,compressFormat: Bitmap.CompressFormat): InputStream {
 | 
			
		||||
    val byteArrayOutputStream = ByteArrayOutputStream()
 | 
			
		||||
 
 | 
			
		||||
@@ -39,7 +39,7 @@
 | 
			
		||||
            app:layout_constraintStart_toStartOf="parent"
 | 
			
		||||
            app:layout_constraintTop_toBottomOf="@+id/itemImage">
 | 
			
		||||
 | 
			
		||||
            <ImageView
 | 
			
		||||
            <bou.amine.apps.readerforselfossv2.android.utils.CircleImageView
 | 
			
		||||
                android:id="@+id/sourceImage"
 | 
			
		||||
                android:layout_width="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_height="wrap_content">
 | 
			
		||||
 | 
			
		||||
    <ImageView
 | 
			
		||||
    <bou.amine.apps.readerforselfossv2.android.utils.CircleImageView
 | 
			
		||||
        android:id="@+id/itemImage"
 | 
			
		||||
        android:layout_width="46dp"
 | 
			
		||||
        android:layout_height="46dp"
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@
 | 
			
		||||
        app:layout_constraintTop_toTopOf="parent"
 | 
			
		||||
        app:layout_constraintVertical_bias="0.0" />
 | 
			
		||||
 | 
			
		||||
    <ImageView
 | 
			
		||||
    <bou.amine.apps.readerforselfossv2.android.utils.CircleImageView
 | 
			
		||||
        android:id="@+id/itemImage"
 | 
			
		||||
        android:layout_width="36dp"
 | 
			
		||||
        android:layout_height="36dp"
 | 
			
		||||
 
 | 
			
		||||
@@ -32,4 +32,10 @@
 | 
			
		||||
        <item name="android:colorBackgroundCacheHint">@null</item>
 | 
			
		||||
        <item name="android:windowIsTranslucent">true</item>
 | 
			
		||||
    </style>
 | 
			
		||||
 | 
			
		||||
    <style name="circleImageView" parent="">
 | 
			
		||||
        <item name="cornerFamily">rounded</item>
 | 
			
		||||
        <item name="cornerSize">50%</item>
 | 
			
		||||
    </style>
 | 
			
		||||
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
@@ -6,9 +6,12 @@ class MercuryModel {
 | 
			
		||||
 | 
			
		||||
    @Serializable
 | 
			
		||||
    class ParsedContent(
 | 
			
		||||
        val title: String?,
 | 
			
		||||
        val content: String?,
 | 
			
		||||
        val lead_image_url: String?, // NOSONAR
 | 
			
		||||
        val url: String
 | 
			
		||||
        val title: String? = null,
 | 
			
		||||
        val content: String? = null,
 | 
			
		||||
        val lead_image_url: String? = null, // NOSONAR
 | 
			
		||||
        val url: String? = null,
 | 
			
		||||
        val error: Boolean? = null,
 | 
			
		||||
        val message: String? = null,
 | 
			
		||||
        val failed: Boolean? = null
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -24,8 +24,8 @@ class SelfossModel {
 | 
			
		||||
    @Serializable
 | 
			
		||||
    class Stats(
 | 
			
		||||
        val total: Int,
 | 
			
		||||
        val unread: Int?,
 | 
			
		||||
        val starred: Int?
 | 
			
		||||
        val unread: Int? = null,
 | 
			
		||||
        val starred: Int? = null
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    @Serializable
 | 
			
		||||
@@ -36,9 +36,9 @@ class SelfossModel {
 | 
			
		||||
 | 
			
		||||
    @Serializable
 | 
			
		||||
    data class ApiInformation(
 | 
			
		||||
        val version: String?,
 | 
			
		||||
        val apiversion: String?,
 | 
			
		||||
        val configuration: ApiConfiguration?
 | 
			
		||||
        val version: String? = null,
 | 
			
		||||
        val apiversion: String? = null,
 | 
			
		||||
        val configuration: ApiConfiguration? = null
 | 
			
		||||
    ) {
 | 
			
		||||
        fun getApiMajorVersion(): Int {
 | 
			
		||||
            var versionNumber = 0
 | 
			
		||||
@@ -54,9 +54,9 @@ class SelfossModel {
 | 
			
		||||
    @Serializable
 | 
			
		||||
    data class ApiConfiguration(
 | 
			
		||||
        @Serializable(with = BooleanSerializer::class)
 | 
			
		||||
        val publicMode: Boolean?,
 | 
			
		||||
        val publicMode: Boolean? = null,
 | 
			
		||||
        @Serializable(with = BooleanSerializer::class)
 | 
			
		||||
        val authEnabled: Boolean?
 | 
			
		||||
        val authEnabled: Boolean? = null
 | 
			
		||||
    ) {
 | 
			
		||||
        fun isAuthEnabled() = authEnabled ?: true
 | 
			
		||||
 | 
			
		||||
@@ -75,7 +75,7 @@ class SelfossModel {
 | 
			
		||||
    data class SourceStats(
 | 
			
		||||
        override val id: Int,
 | 
			
		||||
        override var title: String,
 | 
			
		||||
        override var unread: Int?,
 | 
			
		||||
        override var unread: Int? = null,
 | 
			
		||||
        override var error: String? = null,
 | 
			
		||||
        override var icon: String? = null
 | 
			
		||||
        ) : Source
 | 
			
		||||
@@ -86,11 +86,11 @@ class SelfossModel {
 | 
			
		||||
        override var title: String,
 | 
			
		||||
        override var unread: Int? = null,
 | 
			
		||||
        @Serializable(with = TagsListSerializer::class)
 | 
			
		||||
        var tags: List<String>?,
 | 
			
		||||
        var spout: String?,
 | 
			
		||||
        override var error: String?,
 | 
			
		||||
        override var icon: String?,
 | 
			
		||||
        var params: SourceParams?
 | 
			
		||||
        var tags: List<String>? = null,
 | 
			
		||||
        var spout: String? = null,
 | 
			
		||||
        override var error: String? = null,
 | 
			
		||||
        override var icon: String? = null,
 | 
			
		||||
        var params: SourceParams? = null
 | 
			
		||||
    ) : Source
 | 
			
		||||
 | 
			
		||||
    @Serializable
 | 
			
		||||
@@ -107,13 +107,13 @@ class SelfossModel {
 | 
			
		||||
        var unread: Boolean,
 | 
			
		||||
        @Serializable(with = BooleanSerializer::class)
 | 
			
		||||
        var starred: Boolean,
 | 
			
		||||
        val thumbnail: String?,
 | 
			
		||||
        val icon: String?,
 | 
			
		||||
        val thumbnail: String? = null,
 | 
			
		||||
        val icon: String? = null,
 | 
			
		||||
        val link: String,
 | 
			
		||||
        val sourcetitle: String,
 | 
			
		||||
        @Serializable(with = TagsListSerializer::class)
 | 
			
		||||
        val tags: List<String>,
 | 
			
		||||
        val author: String?
 | 
			
		||||
        val author: String? = null
 | 
			
		||||
    ) {
 | 
			
		||||
        fun getLinkDecoded(): String {
 | 
			
		||||
            var stringUrl: String
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@ import bou.amine.apps.readerforselfossv2.service.AppSettingsService
 | 
			
		||||
import io.github.aakira.napier.Napier
 | 
			
		||||
import io.ktor.client.*
 | 
			
		||||
import io.ktor.client.plugins.*
 | 
			
		||||
import io.ktor.client.plugins.auth.providers.*
 | 
			
		||||
import io.ktor.client.plugins.cache.*
 | 
			
		||||
import io.ktor.client.plugins.contentnegotiation.*
 | 
			
		||||
import io.ktor.client.plugins.cookies.*
 | 
			
		||||
@@ -15,6 +16,9 @@ import io.ktor.client.request.*
 | 
			
		||||
import io.ktor.client.statement.*
 | 
			
		||||
import io.ktor.http.*
 | 
			
		||||
import io.ktor.serialization.kotlinx.json.*
 | 
			
		||||
import io.ktor.util.*
 | 
			
		||||
import io.ktor.utils.io.charsets.*
 | 
			
		||||
import io.ktor.utils.io.core.*
 | 
			
		||||
import kotlinx.coroutines.CoroutineScope
 | 
			
		||||
import kotlinx.coroutines.Dispatchers
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
@@ -32,6 +36,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
                    prettyPrint = true
 | 
			
		||||
                    isLenient = true
 | 
			
		||||
                    ignoreUnknownKeys = true
 | 
			
		||||
                    explicitNulls = false
 | 
			
		||||
                })
 | 
			
		||||
            }
 | 
			
		||||
            install(Logging) {
 | 
			
		||||
@@ -63,6 +68,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
            expectSuccess = false
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        return client
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -74,6 +80,13 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
        client = createHttpClient()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun constructBasicAuthValue(credentials: BasicAuthCredentials): String {
 | 
			
		||||
        val authString = "${credentials.username}:${credentials.password}"
 | 
			
		||||
        val authBuf = authString.toByteArray(Charsets.UTF_8).encodeBase64()
 | 
			
		||||
 | 
			
		||||
        return "Basic $authBuf"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Api version was introduces after the POST login, so when there is a version, it should be available
 | 
			
		||||
    private fun shouldHavePostLogin() = appSettingsService.getApiVersion() != -1
 | 
			
		||||
    private fun hasLoginInfo() =
 | 
			
		||||
@@ -96,11 +109,23 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
    private suspend fun getLogin() = maybeResponse(client.tryToGet(url("/login")) {
 | 
			
		||||
        parameter("username", appSettingsService.getUserName())
 | 
			
		||||
        parameter("password", appSettingsService.getPassword())
 | 
			
		||||
        if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
 | 
			
		||||
            headers {
 | 
			
		||||
                append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    private suspend fun postLogin() = maybeResponse(client.tryToPost(url("/login")) {
 | 
			
		||||
        parameter("username", appSettingsService.getUserName())
 | 
			
		||||
        parameter("password", appSettingsService.getPassword())
 | 
			
		||||
        if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
 | 
			
		||||
            headers {
 | 
			
		||||
                append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    private fun shouldHaveNewLogout() =
 | 
			
		||||
@@ -114,9 +139,23 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    private suspend fun maybeLogoutIfAvailable() =
 | 
			
		||||
        responseOrSuccessIf404(client.tryToGet(url("/logout")))
 | 
			
		||||
        responseOrSuccessIf404(client.tryToGet(url("/logout")) {
 | 
			
		||||
            if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
 | 
			
		||||
                headers {
 | 
			
		||||
                    append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    private suspend fun doLogout() = maybeResponse(client.tryToDelete(url("/api/session/current")))
 | 
			
		||||
    private suspend fun doLogout() = maybeResponse(client.tryToDelete(url("/api/session/current")) {
 | 
			
		||||
        if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
 | 
			
		||||
            headers {
 | 
			
		||||
                append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    suspend fun getItems(
 | 
			
		||||
        type: String,
 | 
			
		||||
@@ -139,6 +178,12 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
            parameter("updatedsince", updatedSince)
 | 
			
		||||
            parameter("items", items ?: appSettingsService.getItemsNumber())
 | 
			
		||||
            parameter("offset", offset)
 | 
			
		||||
            if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
 | 
			
		||||
                headers {
 | 
			
		||||
                    append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    suspend fun getItemsWithoutCatch(): StatusAndData<List<SelfossModel.Item>> =
 | 
			
		||||
@@ -149,6 +194,12 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
            }
 | 
			
		||||
            parameter("type", "all")
 | 
			
		||||
            parameter("items", 1)
 | 
			
		||||
            if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
 | 
			
		||||
                headers {
 | 
			
		||||
                    append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    suspend fun stats(): StatusAndData<SelfossModel.Stats> =
 | 
			
		||||
@@ -157,6 +208,12 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
                parameter("username", appSettingsService.getUserName())
 | 
			
		||||
                parameter("password", appSettingsService.getPassword())
 | 
			
		||||
            }
 | 
			
		||||
            if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
 | 
			
		||||
                headers {
 | 
			
		||||
                    append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    suspend fun tags(): StatusAndData<List<SelfossModel.Tag>> =
 | 
			
		||||
@@ -165,6 +222,12 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
                parameter("username", appSettingsService.getUserName())
 | 
			
		||||
                parameter("password", appSettingsService.getPassword())
 | 
			
		||||
            }
 | 
			
		||||
            if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
 | 
			
		||||
                headers {
 | 
			
		||||
                    append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    suspend fun update(): StatusAndData<String> =
 | 
			
		||||
@@ -173,6 +236,12 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
                parameter("username", appSettingsService.getUserName())
 | 
			
		||||
                parameter("password", appSettingsService.getPassword())
 | 
			
		||||
            }
 | 
			
		||||
            if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
 | 
			
		||||
                headers {
 | 
			
		||||
                    append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    suspend fun spouts(): StatusAndData<Map<String, SelfossModel.Spout>> =
 | 
			
		||||
@@ -181,6 +250,12 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
                parameter("username", appSettingsService.getUserName())
 | 
			
		||||
                parameter("password", appSettingsService.getPassword())
 | 
			
		||||
            }
 | 
			
		||||
            if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
 | 
			
		||||
                headers {
 | 
			
		||||
                    append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    suspend fun sourcesStats(): StatusAndData<ArrayList<SelfossModel.SourceStats>> =
 | 
			
		||||
@@ -189,6 +264,12 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
                parameter("username", appSettingsService.getUserName())
 | 
			
		||||
                parameter("password", appSettingsService.getPassword())
 | 
			
		||||
            }
 | 
			
		||||
            if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
 | 
			
		||||
                headers {
 | 
			
		||||
                    append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    suspend fun sourcesDetailed(): StatusAndData<ArrayList<SelfossModel.SourceDetail>> =
 | 
			
		||||
@@ -197,10 +278,23 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
                parameter("username", appSettingsService.getUserName())
 | 
			
		||||
                parameter("password", appSettingsService.getPassword())
 | 
			
		||||
            }
 | 
			
		||||
            if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
 | 
			
		||||
                headers {
 | 
			
		||||
                    append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    suspend fun apiInformation(): StatusAndData<SelfossModel.ApiInformation> =
 | 
			
		||||
        bodyOrFailure(client.tryToGet(url("/api/about")))
 | 
			
		||||
        bodyOrFailure(client.tryToGet(url("/api/about")) {
 | 
			
		||||
            if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
 | 
			
		||||
                headers {
 | 
			
		||||
                    append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    suspend fun markAsRead(id: String): SuccessResponse =
 | 
			
		||||
        maybeResponse(client.tryToPost(url("/mark/$id")) {
 | 
			
		||||
@@ -208,6 +302,12 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
                parameter("username", appSettingsService.getUserName())
 | 
			
		||||
                parameter("password", appSettingsService.getPassword())
 | 
			
		||||
            }
 | 
			
		||||
            if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
 | 
			
		||||
                headers {
 | 
			
		||||
                    append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    suspend fun unmarkAsRead(id: String): SuccessResponse =
 | 
			
		||||
@@ -216,6 +316,12 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
                parameter("username", appSettingsService.getUserName())
 | 
			
		||||
                parameter("password", appSettingsService.getPassword())
 | 
			
		||||
            }
 | 
			
		||||
            if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
 | 
			
		||||
                headers {
 | 
			
		||||
                    append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    suspend fun starr(id: String): SuccessResponse =
 | 
			
		||||
@@ -224,6 +330,12 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
                parameter("username", appSettingsService.getUserName())
 | 
			
		||||
                parameter("password", appSettingsService.getPassword())
 | 
			
		||||
            }
 | 
			
		||||
            if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
 | 
			
		||||
                headers {
 | 
			
		||||
                    append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    suspend fun unstarr(id: String): SuccessResponse =
 | 
			
		||||
@@ -232,6 +344,12 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
                parameter("username", appSettingsService.getUserName())
 | 
			
		||||
                parameter("password", appSettingsService.getPassword())
 | 
			
		||||
            }
 | 
			
		||||
            if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
 | 
			
		||||
                headers {
 | 
			
		||||
                    append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    suspend fun markAllAsRead(ids: List<String>): SuccessResponse =
 | 
			
		||||
@@ -243,6 +361,14 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
                    append("password", appSettingsService.getPassword())
 | 
			
		||||
                }
 | 
			
		||||
                ids.map { append("ids[]", it) }
 | 
			
		||||
            },
 | 
			
		||||
            block = {
 | 
			
		||||
                if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
 | 
			
		||||
                    headers {
 | 
			
		||||
                        append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
 | 
			
		||||
                        )
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        ))
 | 
			
		||||
 | 
			
		||||
@@ -278,6 +404,14 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
                append("url", url)
 | 
			
		||||
                append("spout", spout)
 | 
			
		||||
                append(tagsParamName, tags)
 | 
			
		||||
            },
 | 
			
		||||
            block = {
 | 
			
		||||
                if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
 | 
			
		||||
                    headers {
 | 
			
		||||
                        append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
 | 
			
		||||
                        )
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
@@ -315,6 +449,14 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
                append("url", url)
 | 
			
		||||
                append("spout", spout)
 | 
			
		||||
                append(tagsParamName, tags)
 | 
			
		||||
            },
 | 
			
		||||
            block = {
 | 
			
		||||
                if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
 | 
			
		||||
                    headers {
 | 
			
		||||
                        append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
 | 
			
		||||
                        )
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
@@ -324,5 +466,18 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
                parameter("username", appSettingsService.getUserName())
 | 
			
		||||
                parameter("password", appSettingsService.getPassword())
 | 
			
		||||
            }
 | 
			
		||||
            if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
 | 
			
		||||
                headers {
 | 
			
		||||
                    append(
 | 
			
		||||
                        HttpHeaders.Authorization,
 | 
			
		||||
                        constructBasicAuthValue(
 | 
			
		||||
                            BasicAuthCredentials(
 | 
			
		||||
                                username = appSettingsService.getBasicUserName(),
 | 
			
		||||
                                password = appSettingsService.getBasicPassword()
 | 
			
		||||
                            )
 | 
			
		||||
                        )
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
}
 | 
			
		||||
@@ -10,7 +10,9 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
 | 
			
		||||
    private var _publicAccess: Boolean? = null
 | 
			
		||||
    private var _baseUrl: String = ""
 | 
			
		||||
    private var _userName: String = ""
 | 
			
		||||
    private var _basicUserName: String = ""
 | 
			
		||||
    private var _password: String = ""
 | 
			
		||||
    private var _basicPassword: String = ""
 | 
			
		||||
 | 
			
		||||
    // User settings related
 | 
			
		||||
    private var _itemsCaching: Boolean? = null
 | 
			
		||||
@@ -96,6 +98,20 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
 | 
			
		||||
        return _password
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun getBasicUserName(): String {
 | 
			
		||||
        if (_basicUserName.isEmpty()) {
 | 
			
		||||
            refreshBasicUsername()
 | 
			
		||||
        }
 | 
			
		||||
        return _basicUserName
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun getBasicPassword(): String {
 | 
			
		||||
        if (_basicPassword.isEmpty()) {
 | 
			
		||||
            refreshBasicPassword()
 | 
			
		||||
        }
 | 
			
		||||
        return _basicPassword
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun getItemsNumber(): Int {
 | 
			
		||||
        if (_itemsNumber == null) {
 | 
			
		||||
            refreshItemsNumber()
 | 
			
		||||
@@ -149,6 +165,14 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
 | 
			
		||||
        _password = settings.getString(PASSWORD, "")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun refreshBasicUsername() {
 | 
			
		||||
        _basicUserName = settings.getString(BASIC_LOGIN, "")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun refreshBasicPassword() {
 | 
			
		||||
        _basicPassword = settings.getString(BASIC_PASSWORD, "")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun refreshArticleViewerEnabled() {
 | 
			
		||||
        _articleViewer = settings.getBoolean(PREFER_ARTICLE_VIEWER, true)
 | 
			
		||||
    }
 | 
			
		||||
@@ -354,6 +378,8 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
 | 
			
		||||
    fun refreshApiSettings() {
 | 
			
		||||
        refreshPassword()
 | 
			
		||||
        refreshUsername()
 | 
			
		||||
        refreshBasicUsername()
 | 
			
		||||
        refreshBasicPassword()
 | 
			
		||||
        refreshBaseUrl()
 | 
			
		||||
        refreshApiVersion()
 | 
			
		||||
        refreshPublicAccess()
 | 
			
		||||
@@ -387,7 +413,17 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
 | 
			
		||||
        login: String,
 | 
			
		||||
        password: String
 | 
			
		||||
    ) {
 | 
			
		||||
        val regex = """\/\/(\D+):(\D+)@""".toRegex()
 | 
			
		||||
        val matchResult = regex.find(url)
 | 
			
		||||
        if (matchResult != null) {
 | 
			
		||||
            val (basicLogin, basicPassword) = matchResult.destructured
 | 
			
		||||
            settings.putString(BASIC_LOGIN, basicLogin)
 | 
			
		||||
            settings.putString(BASIC_PASSWORD, basicPassword)
 | 
			
		||||
            val urlWithoutBasicAuth = url.replace(regex, "//")
 | 
			
		||||
            settings.putString(BASE_URL, urlWithoutBasicAuth)
 | 
			
		||||
        } else {
 | 
			
		||||
            settings.putString(BASE_URL, url)
 | 
			
		||||
        }
 | 
			
		||||
        settings.putString(LOGIN, login)
 | 
			
		||||
        settings.putString(PASSWORD, password)
 | 
			
		||||
        refreshApiSettings()
 | 
			
		||||
@@ -397,6 +433,8 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
 | 
			
		||||
        settings.remove(BASE_URL)
 | 
			
		||||
        settings.remove(LOGIN)
 | 
			
		||||
        settings.remove(PASSWORD)
 | 
			
		||||
        settings.remove(BASIC_LOGIN)
 | 
			
		||||
        settings.remove(BASIC_PASSWORD)
 | 
			
		||||
        refreshApiSettings()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -440,6 +478,10 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
 | 
			
		||||
 | 
			
		||||
        const val PASSWORD = "password"
 | 
			
		||||
 | 
			
		||||
        const val BASIC_LOGIN = "basic_login"
 | 
			
		||||
 | 
			
		||||
        const val BASIC_PASSWORD = "basic_password"
 | 
			
		||||
 | 
			
		||||
        const val PREFER_ARTICLE_VIEWER = "prefer_article_viewer"
 | 
			
		||||
 | 
			
		||||
        const val CARD_VIEW_ACTIVE = "card_view_active"
 | 
			
		||||
@@ -470,7 +512,6 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
 | 
			
		||||
 | 
			
		||||
        const val PERIODIC_REFRESH_MINUTES = "periodic_refresh_minutes"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        const val INFINITE_LOADING = "infinite_loading"
 | 
			
		||||
 | 
			
		||||
        const val ITEMS_CACHING = "items_caching"
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user