forked from Louvorg/ReaderForSelfoss-multiplatform
		
	Compare commits
	
		
			15 Commits
		
	
	
		
			v122123461
			...
			v122123522
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					633b817d76 | ||
| 
						 | 
					2cfaa9b285 | ||
| 
						 | 
					f42ae97326 | ||
| 
						 | 
					3b0028164b | ||
| 
						 | 
					7420adeb5c | ||
| 
						 | 
					316027ca3b | ||
| 
						 | 
					9d58fba5c9 | ||
| 
						 | 
					284c19ef89 | ||
| 
						 | 
					7cfd17231a | ||
| 
						 | 
					527830a5ae | ||
| 
						 | 
					c4ed30f594 | ||
| 
						 | 
					156c1681cf | ||
| 
						 | 
					3593fbca78 | ||
| 
						 | 
					430fc8e8cb | ||
| 
						 | 
					4fce19bad4 | 
							
								
								
									
										22
									
								
								.drone.yml
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								.drone.yml
									
									
									
									
									
								
							@@ -3,27 +3,34 @@ type: docker
 | 
			
		||||
name: test
 | 
			
		||||
 | 
			
		||||
steps:
 | 
			
		||||
  - name: AnylyseBuildTest
 | 
			
		||||
  - name: BuildAndTest
 | 
			
		||||
    image: mingc/android-build-box:latest
 | 
			
		||||
    commands:
 | 
			
		||||
      - echo "---------------------------------------------------------"
 | 
			
		||||
      - echo "Configure gradle..."
 | 
			
		||||
      - mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=true\npushCache=false\nsystemProp.org.gradle.internal.http.connectionTimeout=180000\nsystemProp.org.gradle.internal.http.socketTimeout=180000" >> ~/.gradle/gradle.properties
 | 
			
		||||
      - echo "---------------------------------------------------------"
 | 
			
		||||
      - echo "Analysing..."
 | 
			
		||||
      - ./gradlew sonarqube -Dsonar.projectKey=RFS2 -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN
 | 
			
		||||
      - echo "---------------------------------------------------------"
 | 
			
		||||
      - echo "Building..."
 | 
			
		||||
      - ./gradlew build -x test
 | 
			
		||||
      - echo "---------------------------------------------------------"
 | 
			
		||||
      - echo "Testing..."
 | 
			
		||||
      - echo "---------------------------------------------------------"
 | 
			
		||||
      - ./gradlew test
 | 
			
		||||
      - ./gradlew koverMergedXmlReport
 | 
			
		||||
    environment:
 | 
			
		||||
      SONAR_HOST_URL:
 | 
			
		||||
        from_secret: sonarScannerHostUrl
 | 
			
		||||
      SONAR_LOGIN:
 | 
			
		||||
        from_secret: sonarScannerLogin
 | 
			
		||||
  - name: Analyse
 | 
			
		||||
    image: kytay/sonar-node-plugin
 | 
			
		||||
    settings:
 | 
			
		||||
      sonar_host:
 | 
			
		||||
        from_secret: sonarScannerHostUrl
 | 
			
		||||
      sonar_token:
 | 
			
		||||
        from_secret: sonarScannerLogin
 | 
			
		||||
      use_node_version: 16.18.1
 | 
			
		||||
      sonar_debug: true
 | 
			
		||||
      sonar_project_settings: ./sonar-project.properties
 | 
			
		||||
trigger:
 | 
			
		||||
  event:
 | 
			
		||||
    - push
 | 
			
		||||
@@ -88,9 +95,12 @@ steps:
 | 
			
		||||
  - name: build
 | 
			
		||||
    image: mingc/android-build-box:latest
 | 
			
		||||
    commands:
 | 
			
		||||
      - echo "---------------------------------------------------------"
 | 
			
		||||
      - echo "Fetch tags..."
 | 
			
		||||
      - git fetch --tags
 | 
			
		||||
      - echo "---------------------------------------------------------"
 | 
			
		||||
      - echo "Configure gradle..."
 | 
			
		||||
      - mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=true\npushCache=false\nsystemProp.org.gradle.internal.http.connectionTimeout=180000\nsystemProp.org.gradle.internal.http.socketTimeout=180000" >> ~/.gradle/gradle.properties
 | 
			
		||||
      - mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=false\npushCache=false\nsystemProp.org.gradle.internal.http.connectionTimeout=180000\nsystemProp.org.gradle.internal.http.socketTimeout=180000" >> ~/.gradle/gradle.properties
 | 
			
		||||
      - echo "---------------------------------------------------------"
 | 
			
		||||
      - echo "Generate APK"
 | 
			
		||||
      - ./gradlew :androidApp:assembleGithubConfigRelease  -P pushCache=false
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@ plugins {
 | 
			
		||||
    kotlin("android")
 | 
			
		||||
    kotlin("kapt")
 | 
			
		||||
    id("com.mikepenz.aboutlibraries.plugin")
 | 
			
		||||
    id("org.jetbrains.kotlinx.kover") version "0.6.1"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun Project.execWithOutput(cmd: String, ignore: Boolean = false): String {
 | 
			
		||||
@@ -145,8 +146,8 @@ dependencies {
 | 
			
		||||
    implementation("com.amulyakhare:com.amulyakhare.textdrawable:1.0.1")
 | 
			
		||||
 | 
			
		||||
    // glide
 | 
			
		||||
    kapt("com.github.bumptech.glide:compiler:4.11.0")
 | 
			
		||||
    implementation("com.github.bumptech.glide:okhttp3-integration:4.1.1")
 | 
			
		||||
    kapt("com.github.bumptech.glide:compiler:4.14.2")
 | 
			
		||||
    implementation("com.github.bumptech.glide:okhttp3-integration:4.14.2")
 | 
			
		||||
 | 
			
		||||
    // Themes
 | 
			
		||||
    implementation("com.github.rubensousa:floatingtoolbar:1.5.1")
 | 
			
		||||
@@ -187,9 +188,6 @@ dependencies {
 | 
			
		||||
 | 
			
		||||
    implementation("ch.acra:acra-http:$acraVersion")
 | 
			
		||||
    implementation("ch.acra:acra-toast:$acraVersion")
 | 
			
		||||
 | 
			
		||||
    // Matomo
 | 
			
		||||
    implementation("com.github.matomo-org:matomo-sdk-android:4.1.4")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tasks.withType<Test> {
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,6 @@ import org.acra.ACRA
 | 
			
		||||
import org.acra.ktx.sendSilentlyWithAcra
 | 
			
		||||
 | 
			
		||||
fun Throwable.sendSilentlyWithAcraWithName(name: String) {
 | 
			
		||||
        ACRA.errorReporter.putCustomData("error_source", name)
 | 
			
		||||
    ACRA.errorReporter.putCustomData("error_source", name)
 | 
			
		||||
    this.sendSilentlyWithAcra()
 | 
			
		||||
}
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
package bou.amine.apps.readerforselfossv2.android
 | 
			
		||||
 | 
			
		||||
import android.content.Intent
 | 
			
		||||
import android.net.Uri
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import android.view.Menu
 | 
			
		||||
import android.view.MenuItem
 | 
			
		||||
@@ -40,8 +39,6 @@ import kotlinx.coroutines.launch
 | 
			
		||||
import org.kodein.di.DIAware
 | 
			
		||||
import org.kodein.di.android.closestDI
 | 
			
		||||
import org.kodein.di.instance
 | 
			
		||||
import org.matomo.sdk.Tracker
 | 
			
		||||
import org.matomo.sdk.extra.TrackHelper
 | 
			
		||||
import java.security.MessageDigest
 | 
			
		||||
import java.util.concurrent.TimeUnit
 | 
			
		||||
 | 
			
		||||
@@ -72,7 +69,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
 | 
			
		||||
    override val di by closestDI()
 | 
			
		||||
    private val repository : Repository by instance()
 | 
			
		||||
    private val appSettingsService : AppSettingsService by instance()
 | 
			
		||||
    private val tracker : Tracker by instance()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    override fun onCreate(savedInstanceState: Bundle?) {
 | 
			
		||||
@@ -80,8 +76,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
 | 
			
		||||
        binding = ActivityHomeBinding.inflate(layoutInflater)
 | 
			
		||||
        val view = binding.root
 | 
			
		||||
 | 
			
		||||
        TrackHelper.track().screen("/home").with(tracker)
 | 
			
		||||
 | 
			
		||||
        fromTabShortcut =  intent.getIntExtra("shortcutTab", -1) != -1
 | 
			
		||||
        repository.offlineOverride =  intent.getBooleanExtra("startOffline", false)
 | 
			
		||||
 | 
			
		||||
@@ -600,9 +594,9 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
 | 
			
		||||
                CoroutineScope(Dispatchers.Main).launch {
 | 
			
		||||
                    repository.logout()
 | 
			
		||||
                }
 | 
			
		||||
                this@HomeActivity.finish()
 | 
			
		||||
                val intent = Intent(this, LoginActivity::class.java)
 | 
			
		||||
                this.startActivity(intent)
 | 
			
		||||
                this@HomeActivity.finish()
 | 
			
		||||
                return true
 | 
			
		||||
            }
 | 
			
		||||
            R.id.action_settings -> {
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@ import android.view.MenuItem
 | 
			
		||||
import android.view.View
 | 
			
		||||
import android.view.inputmethod.EditorInfo
 | 
			
		||||
import android.widget.TextView
 | 
			
		||||
import android.widget.Toast
 | 
			
		||||
import androidx.appcompat.app.AlertDialog
 | 
			
		||||
import androidx.appcompat.app.AppCompatActivity
 | 
			
		||||
import androidx.appcompat.app.AppCompatDelegate
 | 
			
		||||
@@ -25,10 +26,6 @@ import kotlinx.coroutines.launch
 | 
			
		||||
import org.kodein.di.DIAware
 | 
			
		||||
import org.kodein.di.android.closestDI
 | 
			
		||||
import org.kodein.di.instance
 | 
			
		||||
import org.matomo.sdk.Tracker
 | 
			
		||||
import org.matomo.sdk.extra.DimensionQueue
 | 
			
		||||
import org.matomo.sdk.extra.DownloadTracker
 | 
			
		||||
import org.matomo.sdk.extra.TrackHelper
 | 
			
		||||
import java.security.MessageDigest
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -40,18 +37,13 @@ class LoginActivity : AppCompatActivity(), DIAware {
 | 
			
		||||
    private lateinit var binding: ActivityLoginBinding
 | 
			
		||||
 | 
			
		||||
    override val di by closestDI()
 | 
			
		||||
    private val repository : Repository by instance()
 | 
			
		||||
    private val appSettingsService : AppSettingsService by instance()
 | 
			
		||||
    private val tracker : Tracker by instance()
 | 
			
		||||
    private val repository: Repository by instance()
 | 
			
		||||
    private val appSettingsService: AppSettingsService by instance()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    override fun onCreate(savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onCreate(savedInstanceState)
 | 
			
		||||
 | 
			
		||||
        TrackHelper.track().download().identifier(DownloadTracker.Extra.ApkChecksum(applicationContext))
 | 
			
		||||
            .with(tracker)
 | 
			
		||||
        TrackHelper.track().screen("/login").with(tracker)
 | 
			
		||||
 | 
			
		||||
        handleTheme()
 | 
			
		||||
 | 
			
		||||
        binding = ActivityLoginBinding.inflate(layoutInflater)
 | 
			
		||||
@@ -64,7 +56,30 @@ class LoginActivity : AppCompatActivity(), DIAware {
 | 
			
		||||
        handleBaseUrlFail()
 | 
			
		||||
 | 
			
		||||
        if (appSettingsService.getBaseUrl().isNotEmpty()) {
 | 
			
		||||
            goToMain()
 | 
			
		||||
            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()
 | 
			
		||||
@@ -114,15 +129,6 @@ class LoginActivity : AppCompatActivity(), DIAware {
 | 
			
		||||
    private fun goToMain() {
 | 
			
		||||
        CoroutineScope(Dispatchers.Main).launch {
 | 
			
		||||
            repository.updateApiVersion()
 | 
			
		||||
 | 
			
		||||
            val messageDigest: MessageDigest = MessageDigest.getInstance("SHA-256")
 | 
			
		||||
            messageDigest.update(appSettingsService.getBaseUrl().toByteArray())
 | 
			
		||||
            tracker.userId = String(messageDigest.digest())
 | 
			
		||||
 | 
			
		||||
            val mDimensionQueue = DimensionQueue(tracker)
 | 
			
		||||
            mDimensionQueue.add(1, appSettingsService.getApiVersion().toString())
 | 
			
		||||
 | 
			
		||||
            tracker.isOptOut = !appSettingsService.isAnalyticsEnabled()
 | 
			
		||||
        }
 | 
			
		||||
        val intent = Intent(this, HomeActivity::class.java)
 | 
			
		||||
        startActivity(intent)
 | 
			
		||||
@@ -191,17 +197,27 @@ class LoginActivity : AppCompatActivity(), DIAware {
 | 
			
		||||
 | 
			
		||||
            repository.refreshLoginInformation(url, login, password)
 | 
			
		||||
 | 
			
		||||
            CoroutineScope(Dispatchers.IO).launch {
 | 
			
		||||
            CoroutineScope(Dispatchers.Main).launch {
 | 
			
		||||
                val result = repository.login()
 | 
			
		||||
                if (result) {
 | 
			
		||||
                    goToMain()
 | 
			
		||||
                } else {
 | 
			
		||||
                    CoroutineScope(Dispatchers.Main).launch {
 | 
			
		||||
                    val (errorFetching, displaySelfossOnly) = repository.shouldBeSelfossInstance()
 | 
			
		||||
                    if (!errorFetching && !displaySelfossOnly) {
 | 
			
		||||
                        goToMain()
 | 
			
		||||
                    } else {
 | 
			
		||||
                        if (displaySelfossOnly) {
 | 
			
		||||
                            Toast.makeText(
 | 
			
		||||
                                applicationContext,
 | 
			
		||||
                                R.string.application_selfoss_only,
 | 
			
		||||
                                Toast.LENGTH_LONG
 | 
			
		||||
                            ).show()
 | 
			
		||||
                        }
 | 
			
		||||
                        preferenceError()
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    preferenceError()
 | 
			
		||||
                }
 | 
			
		||||
                showProgress(false)
 | 
			
		||||
            }
 | 
			
		||||
            showProgress(false)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -215,11 +231,11 @@ class LoginActivity : AppCompatActivity(), DIAware {
 | 
			
		||||
            .alpha(
 | 
			
		||||
                if (show) 0F else 1F
 | 
			
		||||
            ).setListener(object : AnimatorListenerAdapter() {
 | 
			
		||||
            override fun onAnimationEnd(animation: Animator) {
 | 
			
		||||
                binding.loginForm.visibility = if (show) View.GONE else View.VISIBLE
 | 
			
		||||
                override fun onAnimationEnd(animation: Animator) {
 | 
			
		||||
                    binding.loginForm.visibility = if (show) View.GONE else View.VISIBLE
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        )
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        binding.loginProgress.visibility = if (show) View.VISIBLE else View.GONE
 | 
			
		||||
        binding.loginProgress
 | 
			
		||||
@@ -228,11 +244,11 @@ class LoginActivity : AppCompatActivity(), DIAware {
 | 
			
		||||
            .alpha(
 | 
			
		||||
                if (show) 1F else 0F
 | 
			
		||||
            ).setListener(object : AnimatorListenerAdapter() {
 | 
			
		||||
            override fun onAnimationEnd(animation: Animator) {
 | 
			
		||||
                binding.loginProgress.visibility = if (show) View.VISIBLE else View.GONE
 | 
			
		||||
                override fun onAnimationEnd(animation: Animator) {
 | 
			
		||||
                    binding.loginProgress.visibility = if (show) View.VISIBLE else View.GONE
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        )
 | 
			
		||||
            )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onCreateOptionsMenu(menu: Menu): Boolean {
 | 
			
		||||
 
 | 
			
		||||
@@ -35,9 +35,6 @@ import org.acra.data.StringFormat
 | 
			
		||||
import org.acra.ktx.initAcra
 | 
			
		||||
import org.acra.sender.HttpSender
 | 
			
		||||
import org.kodein.di.*
 | 
			
		||||
import org.matomo.sdk.Matomo
 | 
			
		||||
import org.matomo.sdk.Tracker
 | 
			
		||||
import org.matomo.sdk.TrackerBuilder
 | 
			
		||||
 | 
			
		||||
class MyApp : MultiDexApplication(), DIAware {
 | 
			
		||||
 | 
			
		||||
@@ -48,8 +45,6 @@ class MyApp : MultiDexApplication(), DIAware {
 | 
			
		||||
        bind<Repository>() with singleton { Repository(instance(), instance(), isConnectionAvailable, instance()) }
 | 
			
		||||
        bind<ConnectivityStatus>() with singleton { ConnectivityStatus(applicationContext) }
 | 
			
		||||
        bind<AppViewModel>() with singleton { AppViewModel(repository = instance()) }
 | 
			
		||||
        bind<Tracker>() with singleton { TrackerBuilder.createDefault("https://matomo.amine-louveau.fr/matomo.php", if (BuildConfig.DEBUG) 4 else 5).build(
 | 
			
		||||
            Matomo.getInstance(applicationContext)) }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private val repository: Repository by instance()
 | 
			
		||||
 
 | 
			
		||||
@@ -36,9 +36,7 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
 | 
			
		||||
                Snackbar.LENGTH_LONG
 | 
			
		||||
            )
 | 
			
		||||
            .setAction(R.string.undo_string) {
 | 
			
		||||
                CoroutineScope(Dispatchers.IO).launch {
 | 
			
		||||
                    unreadItemAtIndex(item, position, false)
 | 
			
		||||
                }
 | 
			
		||||
                unreadItemAtIndex(item, position, false)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        val view = s.view
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,6 @@ import android.webkit.WebView
 | 
			
		||||
import android.webkit.WebViewClient
 | 
			
		||||
import android.widget.Toast
 | 
			
		||||
import androidx.appcompat.app.AlertDialog
 | 
			
		||||
import androidx.core.content.res.ResourcesCompat
 | 
			
		||||
import androidx.core.widget.NestedScrollView
 | 
			
		||||
import androidx.fragment.app.Fragment
 | 
			
		||||
import bou.amine.apps.readerforselfossv2.android.ImageActivity
 | 
			
		||||
@@ -45,8 +44,6 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton
 | 
			
		||||
import kotlinx.coroutines.CoroutineScope
 | 
			
		||||
import kotlinx.coroutines.Dispatchers
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
import org.acra.ktx.sendSilentlyWithAcra
 | 
			
		||||
import org.acra.ktx.sendWithAcra
 | 
			
		||||
import org.kodein.di.DI
 | 
			
		||||
import org.kodein.di.DIAware
 | 
			
		||||
import org.kodein.di.android.x.closestDI
 | 
			
		||||
@@ -70,7 +67,7 @@ class ArticleFragment : Fragment(), DIAware {
 | 
			
		||||
    private lateinit var fab: FloatingActionButton
 | 
			
		||||
    private lateinit var textAlignment: String
 | 
			
		||||
    private var _binding: FragmentArticleBinding? = null
 | 
			
		||||
    private val binding get() = _binding!!
 | 
			
		||||
    private val binding get() = _binding
 | 
			
		||||
 | 
			
		||||
    override val di : DI by closestDI()
 | 
			
		||||
    private val repository: Repository by instance()
 | 
			
		||||
@@ -113,13 +110,13 @@ class ArticleFragment : Fragment(), DIAware {
 | 
			
		||||
 | 
			
		||||
            refreshAlignment()
 | 
			
		||||
 | 
			
		||||
            fab = binding.fab
 | 
			
		||||
            fab = binding!!.fab
 | 
			
		||||
 | 
			
		||||
            fab.backgroundTintList = ColorStateList.valueOf(resources.getColor(R.color.colorAccent))
 | 
			
		||||
 | 
			
		||||
            fab.rippleColor = resources.getColor(R.color.colorAccentDark)
 | 
			
		||||
 | 
			
		||||
            val floatingToolbar: FloatingToolbar = binding.floatingToolbar
 | 
			
		||||
            val floatingToolbar: FloatingToolbar = binding!!.floatingToolbar
 | 
			
		||||
            floatingToolbar.attachFab(fab)
 | 
			
		||||
 | 
			
		||||
            floatingToolbar.background = ColorDrawable(resources.getColor(R.color.colorAccent))
 | 
			
		||||
@@ -167,35 +164,35 @@ class ArticleFragment : Fragment(), DIAware {
 | 
			
		||||
                floatingToolbar.show()
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            binding.source.text = contentSource
 | 
			
		||||
            binding!!.source.text = contentSource
 | 
			
		||||
            if (typeface != null) {
 | 
			
		||||
                binding.source.typeface = typeface
 | 
			
		||||
                binding!!.source.typeface = typeface
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (contentText.isEmptyOrNullOrNullString()) {
 | 
			
		||||
                getContentFromMercury()
 | 
			
		||||
            } else {
 | 
			
		||||
                binding.titleView.text = contentTitle
 | 
			
		||||
                binding!!.titleView.text = contentTitle
 | 
			
		||||
                if (typeface != null) {
 | 
			
		||||
                    binding.titleView.typeface = typeface
 | 
			
		||||
                    binding!!.titleView.typeface = typeface
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                htmlToWebview()
 | 
			
		||||
 | 
			
		||||
                if (!contentImage.isEmptyOrNullOrNullString() && context != null) {
 | 
			
		||||
                    binding.imageView.visibility = View.VISIBLE
 | 
			
		||||
                    binding!!.imageView.visibility = View.VISIBLE
 | 
			
		||||
                    Glide
 | 
			
		||||
                        .with(requireContext())
 | 
			
		||||
                        .asBitmap()
 | 
			
		||||
                        .load(contentImage)
 | 
			
		||||
                        .apply(RequestOptions.fitCenterTransform())
 | 
			
		||||
                        .into(binding.imageView)
 | 
			
		||||
                        .into(binding!!.imageView)
 | 
			
		||||
                } else {
 | 
			
		||||
                    binding.imageView.visibility = View.GONE
 | 
			
		||||
                    binding!!.imageView.visibility = View.GONE
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            binding.nestedScrollView.setOnScrollChangeListener(
 | 
			
		||||
            binding!!.nestedScrollView.setOnScrollChangeListener(
 | 
			
		||||
                NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
 | 
			
		||||
                    if (scrollY > oldScrollY) {
 | 
			
		||||
                        floatingToolbar.hide()
 | 
			
		||||
@@ -224,7 +221,7 @@ class ArticleFragment : Fragment(), DIAware {
 | 
			
		||||
                .show()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return binding.root
 | 
			
		||||
        return binding!!.root
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDestroyView() {
 | 
			
		||||
@@ -242,16 +239,16 @@ class ArticleFragment : Fragment(), DIAware {
 | 
			
		||||
 | 
			
		||||
    private fun getContentFromMercury() {
 | 
			
		||||
        if (repository.isNetworkAvailable()) {
 | 
			
		||||
            binding.progressBar.visibility = View.VISIBLE
 | 
			
		||||
            binding!!.progressBar.visibility = View.VISIBLE
 | 
			
		||||
 | 
			
		||||
            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()
 | 
			
		||||
                        binding!!.titleView.text = response.data!!.title.orEmpty()
 | 
			
		||||
                        try {
 | 
			
		||||
                            if (typeface != null) {
 | 
			
		||||
                                binding.titleView.typeface = typeface
 | 
			
		||||
                                binding!!.titleView.typeface = typeface
 | 
			
		||||
                            }
 | 
			
		||||
                        } catch (e: Exception) {
 | 
			
		||||
                            e.sendSilentlyWithAcraWithName("getContentFromMercury > typeface")
 | 
			
		||||
@@ -275,7 +272,7 @@ class ArticleFragment : Fragment(), DIAware {
 | 
			
		||||
 | 
			
		||||
                        if (!response.data?.lead_image_url.isNullOrEmpty() && context != null) {
 | 
			
		||||
                            try {
 | 
			
		||||
                                binding.imageView.visibility = View.VISIBLE
 | 
			
		||||
                                binding!!.imageView.visibility = View.VISIBLE
 | 
			
		||||
                                try {
 | 
			
		||||
                                    Glide
 | 
			
		||||
                                        .with(requireContext())
 | 
			
		||||
@@ -284,7 +281,7 @@ class ArticleFragment : Fragment(), DIAware {
 | 
			
		||||
                                            response.data!!.lead_image_url.orEmpty()
 | 
			
		||||
                                        )
 | 
			
		||||
                                        .apply(RequestOptions.fitCenterTransform())
 | 
			
		||||
                                        .into(binding.imageView)
 | 
			
		||||
                                        .into(binding!!.imageView)
 | 
			
		||||
                                } catch (e: Exception) {
 | 
			
		||||
                                    e.sendSilentlyWithAcraWithName("getContentFromMercury > glide lead image")
 | 
			
		||||
                                }
 | 
			
		||||
@@ -292,12 +289,12 @@ class ArticleFragment : Fragment(), DIAware {
 | 
			
		||||
                                e.sendSilentlyWithAcraWithName("getContentFromMercury > outside glide lead image")
 | 
			
		||||
                            }
 | 
			
		||||
                        } else {
 | 
			
		||||
                            binding.imageView.visibility = View.GONE
 | 
			
		||||
                            binding!!.imageView.visibility = View.GONE
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        try {
 | 
			
		||||
                            binding.nestedScrollView.scrollTo(0, 0)
 | 
			
		||||
                            binding.progressBar.visibility = View.GONE
 | 
			
		||||
                            binding!!.nestedScrollView.scrollTo(0, 0)
 | 
			
		||||
                            binding!!.progressBar.visibility = View.GONE
 | 
			
		||||
                        } catch (e: Exception) {
 | 
			
		||||
                            e.sendSilentlyWithAcraWithName("getContentFromMercury > scrollview")
 | 
			
		||||
                        }
 | 
			
		||||
@@ -320,8 +317,8 @@ class ArticleFragment : Fragment(), DIAware {
 | 
			
		||||
        val a: TypedArray = requireContext().obtainStyledAttributes(resId, attrs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        binding.webcontent.settings.standardFontFamily = a.getString(0)
 | 
			
		||||
        binding.webcontent.visibility = View.VISIBLE
 | 
			
		||||
        binding!!.webcontent.settings.standardFontFamily = a.getString(0)
 | 
			
		||||
        binding!!.webcontent.visibility = View.VISIBLE
 | 
			
		||||
 | 
			
		||||
        val colorOnSurface = TypedValue()
 | 
			
		||||
        requireContext().theme.resolveAttribute(R.attr.colorOnSurface, colorOnSurface, true)
 | 
			
		||||
@@ -329,21 +326,21 @@ class ArticleFragment : Fragment(), DIAware {
 | 
			
		||||
        val colorSurface = TypedValue()
 | 
			
		||||
        requireContext().theme.resolveAttribute(R.attr.colorSurface, colorSurface, true)
 | 
			
		||||
 | 
			
		||||
        binding.webcontent.settings.useWideViewPort = true
 | 
			
		||||
        binding.webcontent.settings.loadWithOverviewMode = true
 | 
			
		||||
        binding.webcontent.settings.javaScriptEnabled = false
 | 
			
		||||
        binding!!.webcontent.settings.useWideViewPort = true
 | 
			
		||||
        binding!!.webcontent.settings.loadWithOverviewMode = true
 | 
			
		||||
        binding!!.webcontent.settings.javaScriptEnabled = false
 | 
			
		||||
 | 
			
		||||
        binding.webcontent.webViewClient = object : WebViewClient() {
 | 
			
		||||
        binding!!.webcontent.webViewClient = object : WebViewClient() {
 | 
			
		||||
            @Deprecated("Deprecated in Java")
 | 
			
		||||
            override fun shouldOverrideUrlLoading(view: WebView?, url : String): Boolean {
 | 
			
		||||
                if (binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
 | 
			
		||||
                if (binding!!.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
 | 
			
		||||
                    requireContext().startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
 | 
			
		||||
                }
 | 
			
		||||
                return true
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            @Deprecated("Deprecated in Java")
 | 
			
		||||
            override fun shouldInterceptRequest(view: WebView?, url: String): WebResourceResponse? {
 | 
			
		||||
            override fun shouldInterceptRequest(view: WebView, url: String): WebResourceResponse? {
 | 
			
		||||
                val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
 | 
			
		||||
                if (url.lowercase(Locale.US).contains(".jpg") || url.lowercase(Locale.US).contains(".jpeg")) {
 | 
			
		||||
                    try {
 | 
			
		||||
@@ -380,9 +377,9 @@ class ArticleFragment : Fragment(), DIAware {
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        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
 | 
			
		||||
 | 
			
		||||
        var baseUrl: String? = null
 | 
			
		||||
@@ -413,7 +410,7 @@ class ArticleFragment : Fragment(), DIAware {
 | 
			
		||||
            ""
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        binding.webcontent.loadDataWithBaseURL(
 | 
			
		||||
        binding!!.webcontent.loadDataWithBaseURL(
 | 
			
		||||
            baseUrl,
 | 
			
		||||
            """<html>
 | 
			
		||||
                |<head>
 | 
			
		||||
@@ -466,17 +463,17 @@ class ArticleFragment : Fragment(), DIAware {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun scrollDown() {
 | 
			
		||||
        val height = binding.nestedScrollView.measuredHeight
 | 
			
		||||
        binding.nestedScrollView.smoothScrollBy(0, height/2)
 | 
			
		||||
        val height = binding!!.nestedScrollView.measuredHeight
 | 
			
		||||
        binding!!.nestedScrollView.smoothScrollBy(0, height/2)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun scrollUp() {
 | 
			
		||||
        val height = binding.nestedScrollView.measuredHeight
 | 
			
		||||
        binding.nestedScrollView.smoothScrollBy(0, -height/2)
 | 
			
		||||
        val height = binding!!.nestedScrollView.measuredHeight
 | 
			
		||||
        binding!!.nestedScrollView.smoothScrollBy(0, -height/2)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun openInBrowserAfterFailing() {
 | 
			
		||||
        binding.progressBar.visibility = View.GONE
 | 
			
		||||
        binding!!.progressBar.visibility = View.GONE
 | 
			
		||||
        requireActivity().openInBrowserAsNewTask(this@ArticleFragment.item)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -495,10 +492,10 @@ 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 (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)
 | 
			
		||||
            val position : Int = allImages.indexOf(binding!!.webcontent.hitTestResult.extra)
 | 
			
		||||
 | 
			
		||||
            val intent = Intent(activity, ImageActivity::class.java)
 | 
			
		||||
            intent.putExtra("allImages", allImages)
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,7 @@ class ImageFragment : Fragment() {
 | 
			
		||||
        val view = binding?.root
 | 
			
		||||
 | 
			
		||||
        binding!!.photoView.visibility = View.VISIBLE
 | 
			
		||||
        Glide.with(activity)
 | 
			
		||||
        Glide.with(requireActivity())
 | 
			
		||||
                .asBitmap()
 | 
			
		||||
                .apply(glideOptions)
 | 
			
		||||
                .load(imageUrl)
 | 
			
		||||
 
 | 
			
		||||
@@ -19,13 +19,9 @@ import bou.amine.apps.readerforselfossv2.android.databinding.ActivitySettingsBin
 | 
			
		||||
import bou.amine.apps.readerforselfossv2.android.sendSilentlyWithAcraWithName
 | 
			
		||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
 | 
			
		||||
import com.mikepenz.aboutlibraries.LibsBuilder
 | 
			
		||||
import org.acra.ktx.sendSilentlyWithAcra
 | 
			
		||||
import org.acra.ktx.sendWithAcra
 | 
			
		||||
import org.kodein.di.DIAware
 | 
			
		||||
import org.kodein.di.android.closestDI
 | 
			
		||||
import org.kodein.di.instance
 | 
			
		||||
import org.matomo.sdk.Tracker
 | 
			
		||||
import org.matomo.sdk.extra.TrackHelper
 | 
			
		||||
 | 
			
		||||
private const val TITLE_TAG = "settingsActivityTitle"
 | 
			
		||||
 | 
			
		||||
@@ -33,14 +29,10 @@ class SettingsActivity : AppCompatActivity(),
 | 
			
		||||
        PreferenceFragmentCompat.OnPreferenceStartFragmentCallback, DIAware {
 | 
			
		||||
    override val di by closestDI()
 | 
			
		||||
 | 
			
		||||
    private val tracker : Tracker by instance()
 | 
			
		||||
 | 
			
		||||
    override fun onCreate(savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onCreate(savedInstanceState)
 | 
			
		||||
        val binding = ActivitySettingsBinding.inflate(layoutInflater)
 | 
			
		||||
 | 
			
		||||
        TrackHelper.track().screen("/settings").with(tracker)
 | 
			
		||||
 | 
			
		||||
        setContentView(binding.root)
 | 
			
		||||
        if (savedInstanceState == null) {
 | 
			
		||||
            supportFragmentManager
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +0,0 @@
 | 
			
		||||
<vector android:height="24dp" android:tint="#000000"
 | 
			
		||||
    android:viewportHeight="24" android:viewportWidth="24"
 | 
			
		||||
    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
 | 
			
		||||
    <path android:fillColor="@android:color/white" android:pathData="M21,8c-1.45,0 -2.26,1.44 -1.93,2.51l-3.55,3.56c-0.3,-0.09 -0.74,-0.09 -1.04,0l-2.55,-2.55C12.27,10.45 11.46,9 10,9c-1.45,0 -2.27,1.44 -1.93,2.52l-4.56,4.55C2.44,15.74 1,16.55 1,18c0,1.1 0.9,2 2,2c1.45,0 2.26,-1.44 1.93,-2.51l4.55,-4.56c0.3,0.09 0.74,0.09 1.04,0l2.55,2.55C12.73,16.55 13.54,18 15,18c1.45,0 2.27,-1.44 1.93,-2.52l3.56,-3.55C21.56,12.26 23,11.45 23,10C23,8.9 22.1,8 21,8z"/>
 | 
			
		||||
    <path android:fillColor="@android:color/white" android:pathData="M15,9l0.94,-2.07l2.06,-0.93l-2.06,-0.93l-0.94,-2.07l-0.92,2.07l-2.08,0.93l2.08,0.93z"/>
 | 
			
		||||
    <path android:fillColor="@android:color/white" android:pathData="M3.5,11l0.5,-2l2,-0.5l-2,-0.5l-0.5,-2l-0.5,2l-2,0.5l2,0.5z"/>
 | 
			
		||||
</vector>
 | 
			
		||||
@@ -120,10 +120,10 @@
 | 
			
		||||
    <string name="mode_dark">Dark mode</string>
 | 
			
		||||
    <string name="mode_system">Follow the system setting</string>
 | 
			
		||||
    <string name="mode_light">Light mode</string>
 | 
			
		||||
    <string name="pref_switch_enable_analytics">Enable analytics</string>
 | 
			
		||||
    <string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
 | 
			
		||||
    <string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
 | 
			
		||||
    <string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
 | 
			
		||||
    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
			
		||||
    <string name="menu_home_filter">Filters</string>
 | 
			
		||||
    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
@@ -120,10 +120,10 @@
 | 
			
		||||
    <string name="mode_dark">Dark mode</string>
 | 
			
		||||
    <string name="mode_system">Follow the system setting</string>
 | 
			
		||||
    <string name="mode_light">Light mode</string>
 | 
			
		||||
    <string name="pref_switch_enable_analytics">Enable analytics</string>
 | 
			
		||||
    <string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
 | 
			
		||||
    <string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
 | 
			
		||||
    <string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
 | 
			
		||||
    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
			
		||||
    <string name="menu_home_filter">Filters</string>
 | 
			
		||||
    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
@@ -120,10 +120,10 @@
 | 
			
		||||
    <string name="mode_dark">Dark mode</string>
 | 
			
		||||
    <string name="mode_system">Follow the system setting</string>
 | 
			
		||||
    <string name="mode_light">Light mode</string>
 | 
			
		||||
    <string name="pref_switch_enable_analytics">Enable analytics</string>
 | 
			
		||||
    <string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
 | 
			
		||||
    <string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
 | 
			
		||||
    <string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
 | 
			
		||||
    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
			
		||||
    <string name="menu_home_filter">Filters</string>
 | 
			
		||||
    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
@@ -120,10 +120,10 @@
 | 
			
		||||
    <string name="mode_dark">Dark mode</string>
 | 
			
		||||
    <string name="mode_system">Follow the system setting</string>
 | 
			
		||||
    <string name="mode_light">Light mode</string>
 | 
			
		||||
    <string name="pref_switch_enable_analytics">Enable analytics</string>
 | 
			
		||||
    <string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
 | 
			
		||||
    <string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
 | 
			
		||||
    <string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
 | 
			
		||||
    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
			
		||||
    <string name="menu_home_filter">Filters</string>
 | 
			
		||||
    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
@@ -120,10 +120,10 @@
 | 
			
		||||
    <string name="mode_dark">Thème sombre</string>
 | 
			
		||||
    <string name="mode_system">Utiliser les paramètres système</string>
 | 
			
		||||
    <string name="mode_light">Thème clair</string>
 | 
			
		||||
    <string name="pref_switch_enable_analytics">Enable analytics</string>
 | 
			
		||||
    <string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
 | 
			
		||||
    <string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
 | 
			
		||||
    <string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
 | 
			
		||||
    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
			
		||||
    <string name="menu_home_filter">Filters</string>
 | 
			
		||||
    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
@@ -120,10 +120,10 @@
 | 
			
		||||
    <string name="mode_dark">Dark mode</string>
 | 
			
		||||
    <string name="mode_system">Follow the system setting</string>
 | 
			
		||||
    <string name="mode_light">Light mode</string>
 | 
			
		||||
    <string name="pref_switch_enable_analytics">Enable analytics</string>
 | 
			
		||||
    <string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
 | 
			
		||||
    <string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
 | 
			
		||||
    <string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
 | 
			
		||||
    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
			
		||||
    <string name="menu_home_filter">Filters</string>
 | 
			
		||||
    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
@@ -120,10 +120,10 @@
 | 
			
		||||
    <string name="mode_dark">Dark mode</string>
 | 
			
		||||
    <string name="mode_system">Follow the system setting</string>
 | 
			
		||||
    <string name="mode_light">Light mode</string>
 | 
			
		||||
    <string name="pref_switch_enable_analytics">Enable analytics</string>
 | 
			
		||||
    <string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
 | 
			
		||||
    <string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
 | 
			
		||||
    <string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
 | 
			
		||||
    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
			
		||||
    <string name="menu_home_filter">Filters</string>
 | 
			
		||||
    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
@@ -120,10 +120,10 @@
 | 
			
		||||
    <string name="mode_dark">Dark mode</string>
 | 
			
		||||
    <string name="mode_system">Follow the system setting</string>
 | 
			
		||||
    <string name="mode_light">Light mode</string>
 | 
			
		||||
    <string name="pref_switch_enable_analytics">Enable analytics</string>
 | 
			
		||||
    <string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
 | 
			
		||||
    <string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
 | 
			
		||||
    <string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
 | 
			
		||||
    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
			
		||||
    <string name="menu_home_filter">Filters</string>
 | 
			
		||||
    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
@@ -120,10 +120,10 @@
 | 
			
		||||
    <string name="mode_dark">Dark mode</string>
 | 
			
		||||
    <string name="mode_system">Follow the system setting</string>
 | 
			
		||||
    <string name="mode_light">Light mode</string>
 | 
			
		||||
    <string name="pref_switch_enable_analytics">Enable analytics</string>
 | 
			
		||||
    <string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
 | 
			
		||||
    <string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
 | 
			
		||||
    <string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
 | 
			
		||||
    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
			
		||||
    <string name="menu_home_filter">Filters</string>
 | 
			
		||||
    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
    <string name="pref_switch_enable_analytics">Enable analytics</string>
 | 
			
		||||
    <string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
 | 
			
		||||
    <string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
 | 
			
		||||
    <string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
 | 
			
		||||
    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
			
		||||
    <string name="menu_home_filter">Filters</string>
 | 
			
		||||
    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
			
		||||
</resources>
 | 
			
		||||
@@ -120,10 +120,10 @@
 | 
			
		||||
    <string name="mode_dark">Dark mode</string>
 | 
			
		||||
    <string name="mode_system">Follow the system setting</string>
 | 
			
		||||
    <string name="mode_light">Light mode</string>
 | 
			
		||||
    <string name="pref_switch_enable_analytics">Enable analytics</string>
 | 
			
		||||
    <string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
 | 
			
		||||
    <string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
 | 
			
		||||
    <string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
 | 
			
		||||
    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
			
		||||
    <string name="menu_home_filter">Filters</string>
 | 
			
		||||
    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
@@ -120,10 +120,10 @@
 | 
			
		||||
    <string name="mode_dark">Dark mode</string>
 | 
			
		||||
    <string name="mode_system">Follow the system setting</string>
 | 
			
		||||
    <string name="mode_light">Light mode</string>
 | 
			
		||||
    <string name="pref_switch_enable_analytics">Enable analytics</string>
 | 
			
		||||
    <string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
 | 
			
		||||
    <string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
 | 
			
		||||
    <string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
 | 
			
		||||
    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
			
		||||
    <string name="menu_home_filter">Filters</string>
 | 
			
		||||
    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
@@ -120,10 +120,10 @@
 | 
			
		||||
    <string name="mode_dark">Dark mode</string>
 | 
			
		||||
    <string name="mode_system">Follow the system setting</string>
 | 
			
		||||
    <string name="mode_light">Light mode</string>
 | 
			
		||||
    <string name="pref_switch_enable_analytics">Enable analytics</string>
 | 
			
		||||
    <string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
 | 
			
		||||
    <string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
 | 
			
		||||
    <string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
 | 
			
		||||
    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
			
		||||
    <string name="menu_home_filter">Filters</string>
 | 
			
		||||
    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
@@ -120,10 +120,10 @@
 | 
			
		||||
    <string name="mode_dark">Dark mode</string>
 | 
			
		||||
    <string name="mode_system">Follow the system setting</string>
 | 
			
		||||
    <string name="mode_light">Light mode</string>
 | 
			
		||||
    <string name="pref_switch_enable_analytics">Enable analytics</string>
 | 
			
		||||
    <string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
 | 
			
		||||
    <string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
 | 
			
		||||
    <string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
 | 
			
		||||
    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
			
		||||
    <string name="menu_home_filter">Filters</string>
 | 
			
		||||
    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
@@ -120,10 +120,10 @@
 | 
			
		||||
    <string name="mode_dark">Dark mode</string>
 | 
			
		||||
    <string name="mode_system">Follow the system setting</string>
 | 
			
		||||
    <string name="mode_light">Light mode</string>
 | 
			
		||||
    <string name="pref_switch_enable_analytics">Enable analytics</string>
 | 
			
		||||
    <string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
 | 
			
		||||
    <string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
 | 
			
		||||
    <string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
 | 
			
		||||
    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
			
		||||
    <string name="menu_home_filter">Filters</string>
 | 
			
		||||
    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
@@ -120,10 +120,10 @@
 | 
			
		||||
    <string name="mode_dark">深色模式</string>
 | 
			
		||||
    <string name="mode_system">遵循系统设置</string>
 | 
			
		||||
    <string name="mode_light">浅色模式</string>
 | 
			
		||||
    <string name="pref_switch_enable_analytics">启用分析</string>
 | 
			
		||||
    <string name="gdpr_dialog_title">该应用不分享任何关于您的个人数据。</string>
 | 
			
		||||
    <string name="gdpr_dialog_message"><![CDATA[崩溃报告发送现已启用。 可以从设置页面禁用它。 请记住,崩溃报告对于应用程序开发是必需的。]]></string>
 | 
			
		||||
    <string name="crash_toast_text">发生崩溃。请将细节发送给开发人员。</string>
 | 
			
		||||
    <string name="pref_switch_disable_acra">"禁用自动错误报告 "</string>
 | 
			
		||||
    <string name="menu_home_filter">筛选器</string>
 | 
			
		||||
    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
@@ -120,10 +120,10 @@
 | 
			
		||||
    <string name="mode_dark">Dark mode</string>
 | 
			
		||||
    <string name="mode_system">Follow the system setting</string>
 | 
			
		||||
    <string name="mode_light">Light mode</string>
 | 
			
		||||
    <string name="pref_switch_enable_analytics">Enable analytics</string>
 | 
			
		||||
    <string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
 | 
			
		||||
    <string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
 | 
			
		||||
    <string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
 | 
			
		||||
    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
			
		||||
    <string name="menu_home_filter">Filters</string>
 | 
			
		||||
    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
@@ -105,7 +105,7 @@
 | 
			
		||||
    <string name="new_items_notification_text">%1$d new items loaded.</string>
 | 
			
		||||
    <string name="pref_switch_notify_new_items">Notify on new items synced.</string>
 | 
			
		||||
    <string name="shortcut_offline">Offline</string>
 | 
			
		||||
    <string name="pref_api_timeout">Api Timeout</string>
 | 
			
		||||
    <string name="pref_api_timeout">Api Timeout (seconds)</string>
 | 
			
		||||
    <string name="pref_header_experimental">Experimental</string>
 | 
			
		||||
    <string name="webview_dialog_issue_message">Webview not available. Disabling the article viewer to avoid any future crashes. Will load articles inside of your browser from now on.</string>
 | 
			
		||||
    <string name="webview_dialog_issue_title">Webview issue</string>
 | 
			
		||||
@@ -123,10 +123,10 @@
 | 
			
		||||
    <string name="mode_dark">Dark mode</string>
 | 
			
		||||
    <string name="mode_system">Follow the system setting</string>
 | 
			
		||||
    <string name="mode_light">Light mode</string>
 | 
			
		||||
    <string name="pref_switch_enable_analytics">Enable analytics</string>
 | 
			
		||||
    <string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
 | 
			
		||||
    <string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
 | 
			
		||||
    <string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
 | 
			
		||||
    <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
 | 
			
		||||
    <string name="menu_home_filter">Filters</string>
 | 
			
		||||
    <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
@@ -38,13 +38,6 @@
 | 
			
		||||
        android:icon="@drawable/ic_widgets_black_24dp" />
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    <SwitchPreference
 | 
			
		||||
        android:defaultValue="false"
 | 
			
		||||
        android:key="enable_analytics"
 | 
			
		||||
        android:title="@string/pref_switch_enable_analytics"
 | 
			
		||||
        android:icon="@drawable/ic_baseline_insights_24"/>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    <SwitchPreference
 | 
			
		||||
        android:defaultValue="false"
 | 
			
		||||
        android:key="acra.disable"
 | 
			
		||||
 
 | 
			
		||||
@@ -369,14 +369,7 @@ class RepositoryTest {
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun get_tags() {
 | 
			
		||||
        val tags = listOf(
 | 
			
		||||
            SelfossModel.Tag("test", "red", 6),
 | 
			
		||||
            SelfossModel.Tag("second", "yellow", 0)
 | 
			
		||||
        )
 | 
			
		||||
        val tagsDB = listOf(
 | 
			
		||||
            TAG("test_DB", "red", 6),
 | 
			
		||||
            TAG("second_DB", "yellow", 0)
 | 
			
		||||
        )
 | 
			
		||||
        val (tags, tagsDB) = prepareTags()
 | 
			
		||||
 | 
			
		||||
        coEvery { api.tags() } returns StatusAndData(success = true, data = tags)
 | 
			
		||||
        coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB
 | 
			
		||||
@@ -396,17 +389,7 @@ class RepositoryTest {
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun get_tags_with_sources_update_disabled() {
 | 
			
		||||
        val tags = listOf(
 | 
			
		||||
            SelfossModel.Tag("test", "red", 6),
 | 
			
		||||
            SelfossModel.Tag("second", "yellow", 0)
 | 
			
		||||
        )
 | 
			
		||||
        val tagsDB = listOf(
 | 
			
		||||
            TAG("test_DB", "red", 6),
 | 
			
		||||
            TAG("second_DB", "yellow", 0)
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        coEvery { api.tags() } returns StatusAndData(success = true, data = tags)
 | 
			
		||||
        coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB
 | 
			
		||||
        val (tags, tagsDB) = prepareTags()
 | 
			
		||||
        every { appSettingsService.isUpdateSourcesEnabled() } returns false
 | 
			
		||||
        every { appSettingsService.isItemCachingEnabled() } returns true
 | 
			
		||||
 | 
			
		||||
@@ -426,17 +409,7 @@ class RepositoryTest {
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun get_tags_with_items_caching_disabled() {
 | 
			
		||||
        val tags = listOf(
 | 
			
		||||
            SelfossModel.Tag("test", "red", 6),
 | 
			
		||||
            SelfossModel.Tag("second", "yellow", 0)
 | 
			
		||||
        )
 | 
			
		||||
        val tagsDB = listOf(
 | 
			
		||||
            TAG("test_DB", "red", 6),
 | 
			
		||||
            TAG("second_DB", "yellow", 0)
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        coEvery { api.tags() } returns StatusAndData(success = true, data = tags)
 | 
			
		||||
        coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB
 | 
			
		||||
        val (tags, _) = prepareTags()
 | 
			
		||||
        every { appSettingsService.isUpdateSourcesEnabled() } returns true
 | 
			
		||||
        every { appSettingsService.isItemCachingEnabled() } returns false
 | 
			
		||||
 | 
			
		||||
@@ -453,17 +426,7 @@ class RepositoryTest {
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun get_tags_with_sources_update_and_items_caching_disabled() {
 | 
			
		||||
        val tags = listOf(
 | 
			
		||||
            SelfossModel.Tag("test", "red", 6),
 | 
			
		||||
            SelfossModel.Tag("second", "yellow", 0)
 | 
			
		||||
        )
 | 
			
		||||
        val tagsDB = listOf(
 | 
			
		||||
            TAG("test_DB", "red", 6),
 | 
			
		||||
            TAG("second_DB", "yellow", 0)
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        coEvery { api.tags() } returns StatusAndData(success = true, data = tags)
 | 
			
		||||
        coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB
 | 
			
		||||
        val (tags, tagsDB) = prepareTags()
 | 
			
		||||
        every { appSettingsService.isUpdateSourcesEnabled() } returns false
 | 
			
		||||
        every { appSettingsService.isItemCachingEnabled() } returns false
 | 
			
		||||
 | 
			
		||||
@@ -482,17 +445,7 @@ class RepositoryTest {
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun get_tags_without_connection() {
 | 
			
		||||
        val tags = listOf(
 | 
			
		||||
            SelfossModel.Tag("test", "red", 6),
 | 
			
		||||
            SelfossModel.Tag("second", "yellow", 0)
 | 
			
		||||
        )
 | 
			
		||||
        val tagsDB = listOf(
 | 
			
		||||
            TAG("test_DB", "red", 6),
 | 
			
		||||
            TAG("second_DB", "yellow", 0)
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        coEvery { api.tags() } returns StatusAndData(success = true, data = tags)
 | 
			
		||||
        coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB
 | 
			
		||||
        val (tags, tagsDB) = prepareTags()
 | 
			
		||||
        every { appSettingsService.isUpdateSourcesEnabled() } returns true
 | 
			
		||||
        every { appSettingsService.isItemCachingEnabled() } returns true
 | 
			
		||||
 | 
			
		||||
@@ -510,17 +463,7 @@ class RepositoryTest {
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun get_tags_without_connection_and_items_caching_disabled() {
 | 
			
		||||
        val tags = listOf(
 | 
			
		||||
            SelfossModel.Tag("test", "red", 6),
 | 
			
		||||
            SelfossModel.Tag("second", "yellow", 0)
 | 
			
		||||
        )
 | 
			
		||||
        val tagsDB = listOf(
 | 
			
		||||
            TAG("test_DB", "red", 6),
 | 
			
		||||
            TAG("second_DB", "yellow", 0)
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        coEvery { api.tags() } returns StatusAndData(success = true, data = tags)
 | 
			
		||||
        coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB
 | 
			
		||||
        prepareTags()
 | 
			
		||||
        every { appSettingsService.isItemCachingEnabled() } returns false
 | 
			
		||||
        every { appSettingsService.isUpdateSourcesEnabled() } returns true
 | 
			
		||||
 | 
			
		||||
@@ -537,17 +480,7 @@ class RepositoryTest {
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun get_tags_without_connection_and_sources_update_disabled() {
 | 
			
		||||
        val tags = listOf(
 | 
			
		||||
            SelfossModel.Tag("test", "red", 6),
 | 
			
		||||
            SelfossModel.Tag("second", "yellow", 0)
 | 
			
		||||
        )
 | 
			
		||||
        val tagsDB = listOf(
 | 
			
		||||
            TAG("test_DB", "red", 6),
 | 
			
		||||
            TAG("second_DB", "yellow", 0)
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        coEvery { api.tags() } returns StatusAndData(success = true, data = tags)
 | 
			
		||||
        coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB
 | 
			
		||||
        val (tags, tagsDB) = prepareTags()
 | 
			
		||||
        every { appSettingsService.isUpdateSourcesEnabled() } returns false
 | 
			
		||||
        every { appSettingsService.isItemCachingEnabled() } returns true
 | 
			
		||||
 | 
			
		||||
@@ -565,17 +498,7 @@ class RepositoryTest {
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun get_tags_without_connection_and_sources_update_and_items_caching_disabled() {
 | 
			
		||||
        val tags = listOf(
 | 
			
		||||
            SelfossModel.Tag("test", "red", 6),
 | 
			
		||||
            SelfossModel.Tag("second", "yellow", 0)
 | 
			
		||||
        )
 | 
			
		||||
        val tagsDB = listOf(
 | 
			
		||||
            TAG("test_DB", "red", 6),
 | 
			
		||||
            TAG("second_DB", "yellow", 0)
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        coEvery { api.tags() } returns StatusAndData(success = true, data = tags)
 | 
			
		||||
        coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB
 | 
			
		||||
        val (_, tagsDB) = prepareTags()
 | 
			
		||||
        every { appSettingsService.isUpdateSourcesEnabled() } returns false
 | 
			
		||||
        every { appSettingsService.isItemCachingEnabled() } returns false
 | 
			
		||||
 | 
			
		||||
@@ -590,8 +513,36 @@ class RepositoryTest {
 | 
			
		||||
        verify(atLeast = 1) { db.tagsQueries.tags().executeAsList() }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun prepareTags(): Pair<List<SelfossModel.Tag>, List<TAG>> {
 | 
			
		||||
        val tags = listOf(
 | 
			
		||||
            SelfossModel.Tag("test", "red", 6),
 | 
			
		||||
            SelfossModel.Tag("second", "yellow", 0)
 | 
			
		||||
        )
 | 
			
		||||
        val tagsDB = listOf(
 | 
			
		||||
            TAG("test_DB", "red", 6),
 | 
			
		||||
            TAG("second_DB", "yellow", 0)
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        coEvery { api.tags() } returns StatusAndData(success = true, data = tags)
 | 
			
		||||
        coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB
 | 
			
		||||
        return Pair(tags, tagsDB)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun get_sources() {
 | 
			
		||||
        val (sources, sourcesDB) = prepareSources()
 | 
			
		||||
        initializeRepository()
 | 
			
		||||
        var testSources: List<SelfossModel.Source>?
 | 
			
		||||
        runBlocking {
 | 
			
		||||
            testSources = repository.getSources()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        assertSame(sources, testSources)
 | 
			
		||||
        assertNotEquals(sourcesDB.map { it.toView() }, testSources)
 | 
			
		||||
        coVerify(exactly = 1) { api.sources() }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun prepareSources(): Pair<ArrayList<SelfossModel.Source>, List<SOURCE>> {
 | 
			
		||||
        val sources = arrayListOf(
 | 
			
		||||
            SelfossModel.Source(
 | 
			
		||||
                1,
 | 
			
		||||
@@ -631,60 +582,15 @@ class RepositoryTest {
 | 
			
		||||
 | 
			
		||||
        coEvery { api.sources() } returns StatusAndData(success = true, data = sources)
 | 
			
		||||
        every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB
 | 
			
		||||
        initializeRepository()
 | 
			
		||||
        var testSources: List<SelfossModel.Source>?
 | 
			
		||||
        runBlocking {
 | 
			
		||||
            testSources = repository.getSources()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        assertSame(sources, testSources)
 | 
			
		||||
        assertNotEquals(sourcesDB.map { it.toView() }, testSources)
 | 
			
		||||
        coVerify(exactly = 1) { api.sources() }
 | 
			
		||||
        return Pair(sources, sourcesDB)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun get_sources_with_sources_update_disabled() {
 | 
			
		||||
        val sources = arrayListOf(
 | 
			
		||||
            SelfossModel.Source(
 | 
			
		||||
                1,
 | 
			
		||||
                "First source",
 | 
			
		||||
                listOf("Test", "second"),
 | 
			
		||||
                "spouts\\rss\\fulltextrss",
 | 
			
		||||
                "",
 | 
			
		||||
                "d8c92cdb1ef119ea85c4b9205c879ca7.png"
 | 
			
		||||
            ),
 | 
			
		||||
            SelfossModel.Source(
 | 
			
		||||
                2,
 | 
			
		||||
                "Second DB source",
 | 
			
		||||
                listOf("second"),
 | 
			
		||||
                "spouts\\rss\\fulltextrss",
 | 
			
		||||
                "",
 | 
			
		||||
                "b3aa8a664d08eb15d6ff1db2fa83e0d9.png"
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
        val sourcesDB = listOf(
 | 
			
		||||
            SOURCE(
 | 
			
		||||
                "1",
 | 
			
		||||
                "First source",
 | 
			
		||||
                "Test,second",
 | 
			
		||||
                "spouts\\rss\\fulltextrss",
 | 
			
		||||
                "",
 | 
			
		||||
                "d8c92cdb1ef119ea85c4b9205c879ca7.png"
 | 
			
		||||
            ),
 | 
			
		||||
            SOURCE(
 | 
			
		||||
                "2",
 | 
			
		||||
                "Second source",
 | 
			
		||||
                "second",
 | 
			
		||||
                "spouts\\rss\\fulltextrss",
 | 
			
		||||
                "",
 | 
			
		||||
                "b3aa8a664d08eb15d6ff1db2fa83e0d9.png"
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
        val (sources, sourcesDB) = prepareSources()
 | 
			
		||||
 | 
			
		||||
        every { appSettingsService.isUpdateSourcesEnabled() } returns false
 | 
			
		||||
        every { appSettingsService.isItemCachingEnabled() } returns true
 | 
			
		||||
        coEvery { api.sources() } returns StatusAndData(success = true, data = sources)
 | 
			
		||||
        every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB
 | 
			
		||||
        initializeRepository()
 | 
			
		||||
        var testSources: List<SelfossModel.Source>?
 | 
			
		||||
        runBlocking {
 | 
			
		||||
@@ -701,47 +607,10 @@ class RepositoryTest {
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun get_sources_with_items_caching_disabled() {
 | 
			
		||||
        val sources = arrayListOf(
 | 
			
		||||
            SelfossModel.Source(
 | 
			
		||||
                1,
 | 
			
		||||
                "First source",
 | 
			
		||||
                listOf("Test", "second"),
 | 
			
		||||
                "spouts\\rss\\fulltextrss",
 | 
			
		||||
                "",
 | 
			
		||||
                "d8c92cdb1ef119ea85c4b9205c879ca7.png"
 | 
			
		||||
            ),
 | 
			
		||||
            SelfossModel.Source(
 | 
			
		||||
                2,
 | 
			
		||||
                "Second source",
 | 
			
		||||
                listOf("second"),
 | 
			
		||||
                "spouts\\rss\\fulltextrss",
 | 
			
		||||
                "",
 | 
			
		||||
                "b3aa8a664d08eb15d6ff1db2fa83e0d9.png"
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
        val sourcesDB = listOf(
 | 
			
		||||
            SOURCE(
 | 
			
		||||
                "1",
 | 
			
		||||
                "First source",
 | 
			
		||||
                "Test,second",
 | 
			
		||||
                "spouts\\rss\\fulltextrss",
 | 
			
		||||
                "",
 | 
			
		||||
                "d8c92cdb1ef119ea85c4b9205c879ca7.png"
 | 
			
		||||
            ),
 | 
			
		||||
            SOURCE(
 | 
			
		||||
                "2",
 | 
			
		||||
                "Second source",
 | 
			
		||||
                "second",
 | 
			
		||||
                "spouts\\rss\\fulltextrss",
 | 
			
		||||
                "",
 | 
			
		||||
                "b3aa8a664d08eb15d6ff1db2fa83e0d9.png"
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
        val (sources, _) = prepareSources()
 | 
			
		||||
 | 
			
		||||
        every { appSettingsService.isUpdateSourcesEnabled() } returns true
 | 
			
		||||
        every { appSettingsService.isItemCachingEnabled() } returns false
 | 
			
		||||
        coEvery { api.sources() } returns StatusAndData(success = true, data = sources)
 | 
			
		||||
        every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB
 | 
			
		||||
        initializeRepository()
 | 
			
		||||
        var testSources: List<SelfossModel.Source>?
 | 
			
		||||
        runBlocking {
 | 
			
		||||
@@ -755,47 +624,10 @@ class RepositoryTest {
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun get_sources_with_sources_update_and_items_caching_disabled() {
 | 
			
		||||
        val sources = arrayListOf(
 | 
			
		||||
            SelfossModel.Source(
 | 
			
		||||
                1,
 | 
			
		||||
                "First source",
 | 
			
		||||
                listOf("Test", "second"),
 | 
			
		||||
                "spouts\\rss\\fulltextrss",
 | 
			
		||||
                "",
 | 
			
		||||
                "d8c92cdb1ef119ea85c4b9205c879ca7.png"
 | 
			
		||||
            ),
 | 
			
		||||
            SelfossModel.Source(
 | 
			
		||||
                2,
 | 
			
		||||
                "Second source",
 | 
			
		||||
                listOf("second"),
 | 
			
		||||
                "spouts\\rss\\fulltextrss",
 | 
			
		||||
                "",
 | 
			
		||||
                "b3aa8a664d08eb15d6ff1db2fa83e0d9.png"
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
        val sourcesDB = listOf(
 | 
			
		||||
            SOURCE(
 | 
			
		||||
                "1",
 | 
			
		||||
                "First source",
 | 
			
		||||
                "Test,second",
 | 
			
		||||
                "spouts\\rss\\fulltextrss",
 | 
			
		||||
                "",
 | 
			
		||||
                "d8c92cdb1ef119ea85c4b9205c879ca7.png"
 | 
			
		||||
            ),
 | 
			
		||||
            SOURCE(
 | 
			
		||||
                "2",
 | 
			
		||||
                "Second source",
 | 
			
		||||
                "second",
 | 
			
		||||
                "spouts\\rss\\fulltextrss",
 | 
			
		||||
                "",
 | 
			
		||||
                "b3aa8a664d08eb15d6ff1db2fa83e0d9.png"
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
        val (sources, _) = prepareSources()
 | 
			
		||||
 | 
			
		||||
        every { appSettingsService.isUpdateSourcesEnabled() } returns false
 | 
			
		||||
        every { appSettingsService.isItemCachingEnabled() } returns false
 | 
			
		||||
        coEvery { api.sources() } returns StatusAndData(success = true, data = sources)
 | 
			
		||||
        every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB
 | 
			
		||||
        initializeRepository()
 | 
			
		||||
        var testSources: List<SelfossModel.Source>?
 | 
			
		||||
        runBlocking {
 | 
			
		||||
@@ -809,45 +641,7 @@ class RepositoryTest {
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun get_sources_without_connection() {
 | 
			
		||||
        val sources = arrayListOf(
 | 
			
		||||
            SelfossModel.Source(
 | 
			
		||||
                1,
 | 
			
		||||
                "First source",
 | 
			
		||||
                listOf("Test", "second"),
 | 
			
		||||
                "spouts\\rss\\fulltextrss",
 | 
			
		||||
                "",
 | 
			
		||||
                "d8c92cdb1ef119ea85c4b9205c879ca7.png"
 | 
			
		||||
            ),
 | 
			
		||||
            SelfossModel.Source(
 | 
			
		||||
                2,
 | 
			
		||||
                "Second source",
 | 
			
		||||
                listOf("second"),
 | 
			
		||||
                "spouts\\rss\\fulltextrss",
 | 
			
		||||
                "",
 | 
			
		||||
                "b3aa8a664d08eb15d6ff1db2fa83e0d9.png"
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
        val sourcesDB = listOf(
 | 
			
		||||
            SOURCE(
 | 
			
		||||
                "1",
 | 
			
		||||
                "First DB source",
 | 
			
		||||
                "Test,second",
 | 
			
		||||
                "spouts\\rss\\fulltextrss",
 | 
			
		||||
                "",
 | 
			
		||||
                "d8c92cdb1ef119ea85c4b9205c879ca7.png"
 | 
			
		||||
            ),
 | 
			
		||||
            SOURCE(
 | 
			
		||||
                "2",
 | 
			
		||||
                "Second source",
 | 
			
		||||
                "second",
 | 
			
		||||
                "spouts\\rss\\fulltextrss",
 | 
			
		||||
                "",
 | 
			
		||||
                "b3aa8a664d08eb15d6ff1db2fa83e0d9.png"
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        coEvery { api.sources() } returns StatusAndData(success = true, data = sources)
 | 
			
		||||
        every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB
 | 
			
		||||
        val (_, sourcesDB) = prepareSources()
 | 
			
		||||
        initializeRepository(MutableStateFlow(false))
 | 
			
		||||
        var testSources: List<SelfossModel.Source>?
 | 
			
		||||
        runBlocking {
 | 
			
		||||
@@ -861,47 +655,10 @@ class RepositoryTest {
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun get_sources_without_connection_and_items_caching_disabled() {
 | 
			
		||||
        val sources = arrayListOf(
 | 
			
		||||
            SelfossModel.Source(
 | 
			
		||||
                1,
 | 
			
		||||
                "First source",
 | 
			
		||||
                listOf("Test", "second"),
 | 
			
		||||
                "spouts\\rss\\fulltextrss",
 | 
			
		||||
                "",
 | 
			
		||||
                "d8c92cdb1ef119ea85c4b9205c879ca7.png"
 | 
			
		||||
            ),
 | 
			
		||||
            SelfossModel.Source(
 | 
			
		||||
                2,
 | 
			
		||||
                "Second source",
 | 
			
		||||
                listOf("second"),
 | 
			
		||||
                "spouts\\rss\\fulltextrss",
 | 
			
		||||
                "",
 | 
			
		||||
                "b3aa8a664d08eb15d6ff1db2fa83e0d9.png"
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
        val sourcesDB = listOf(
 | 
			
		||||
            SOURCE(
 | 
			
		||||
                "1",
 | 
			
		||||
                "First DB source",
 | 
			
		||||
                "Test,second",
 | 
			
		||||
                "spouts\\rss\\fulltextrss",
 | 
			
		||||
                "",
 | 
			
		||||
                "d8c92cdb1ef119ea85c4b9205c879ca7.png"
 | 
			
		||||
            ),
 | 
			
		||||
            SOURCE(
 | 
			
		||||
                "2",
 | 
			
		||||
                "Second source",
 | 
			
		||||
                "second",
 | 
			
		||||
                "spouts\\rss\\fulltextrss",
 | 
			
		||||
                "",
 | 
			
		||||
                "b3aa8a664d08eb15d6ff1db2fa83e0d9.png"
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
        prepareSources()
 | 
			
		||||
 | 
			
		||||
        every { appSettingsService.isItemCachingEnabled() } returns false
 | 
			
		||||
        every { appSettingsService.isUpdateSourcesEnabled() } returns true
 | 
			
		||||
        coEvery { api.sources() } returns StatusAndData(success = true, data = sources)
 | 
			
		||||
        every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB
 | 
			
		||||
        initializeRepository(MutableStateFlow(false))
 | 
			
		||||
        var testSources: List<SelfossModel.Source>?
 | 
			
		||||
        runBlocking {
 | 
			
		||||
@@ -915,47 +672,10 @@ class RepositoryTest {
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun get_sources_without_connection_and_sources_update_disabled() {
 | 
			
		||||
        val sources = arrayListOf(
 | 
			
		||||
            SelfossModel.Source(
 | 
			
		||||
                1,
 | 
			
		||||
                "First source",
 | 
			
		||||
                listOf("Test", "second"),
 | 
			
		||||
                "spouts\\rss\\fulltextrss",
 | 
			
		||||
                "",
 | 
			
		||||
                "d8c92cdb1ef119ea85c4b9205c879ca7.png"
 | 
			
		||||
            ),
 | 
			
		||||
            SelfossModel.Source(
 | 
			
		||||
                2,
 | 
			
		||||
                "Second source",
 | 
			
		||||
                listOf("second"),
 | 
			
		||||
                "spouts\\rss\\fulltextrss",
 | 
			
		||||
                "",
 | 
			
		||||
                "b3aa8a664d08eb15d6ff1db2fa83e0d9.png"
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
        val sourcesDB = listOf(
 | 
			
		||||
            SOURCE(
 | 
			
		||||
                "1",
 | 
			
		||||
                "First DB source",
 | 
			
		||||
                "Test,second",
 | 
			
		||||
                "spouts\\rss\\fulltextrss",
 | 
			
		||||
                "",
 | 
			
		||||
                "d8c92cdb1ef119ea85c4b9205c879ca7.png"
 | 
			
		||||
            ),
 | 
			
		||||
            SOURCE(
 | 
			
		||||
                "2",
 | 
			
		||||
                "Second source",
 | 
			
		||||
                "second",
 | 
			
		||||
                "spouts\\rss\\fulltextrss",
 | 
			
		||||
                "",
 | 
			
		||||
                "b3aa8a664d08eb15d6ff1db2fa83e0d9.png"
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
        val (_, sourcesDB) = prepareSources()
 | 
			
		||||
 | 
			
		||||
        every { appSettingsService.isItemCachingEnabled() } returns true
 | 
			
		||||
        every { appSettingsService.isUpdateSourcesEnabled() } returns false
 | 
			
		||||
        coEvery { api.sources() } returns StatusAndData(success = true, data = sources)
 | 
			
		||||
        every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB
 | 
			
		||||
        initializeRepository(MutableStateFlow(false))
 | 
			
		||||
        var testSources: List<SelfossModel.Source>?
 | 
			
		||||
        runBlocking {
 | 
			
		||||
@@ -969,47 +689,10 @@ class RepositoryTest {
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun get_sources_without_connection_and_items_caching_and_sources_update_disabled() {
 | 
			
		||||
        val sources = arrayListOf(
 | 
			
		||||
            SelfossModel.Source(
 | 
			
		||||
                1,
 | 
			
		||||
                "First source",
 | 
			
		||||
                listOf("Test", "second"),
 | 
			
		||||
                "spouts\\rss\\fulltextrss",
 | 
			
		||||
                "",
 | 
			
		||||
                "d8c92cdb1ef119ea85c4b9205c879ca7.png"
 | 
			
		||||
            ),
 | 
			
		||||
            SelfossModel.Source(
 | 
			
		||||
                2,
 | 
			
		||||
                "Second source",
 | 
			
		||||
                listOf("second"),
 | 
			
		||||
                "spouts\\rss\\fulltextrss",
 | 
			
		||||
                "",
 | 
			
		||||
                "b3aa8a664d08eb15d6ff1db2fa83e0d9.png"
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
        val sourcesDB = listOf(
 | 
			
		||||
            SOURCE(
 | 
			
		||||
                "1",
 | 
			
		||||
                "First DB source",
 | 
			
		||||
                "Test,second",
 | 
			
		||||
                "spouts\\rss\\fulltextrss",
 | 
			
		||||
                "",
 | 
			
		||||
                "d8c92cdb1ef119ea85c4b9205c879ca7.png"
 | 
			
		||||
            ),
 | 
			
		||||
            SOURCE(
 | 
			
		||||
                "2",
 | 
			
		||||
                "Second source",
 | 
			
		||||
                "second",
 | 
			
		||||
                "spouts\\rss\\fulltextrss",
 | 
			
		||||
                "",
 | 
			
		||||
                "b3aa8a664d08eb15d6ff1db2fa83e0d9.png"
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
        val (_, sourcesDB) = prepareSources()
 | 
			
		||||
 | 
			
		||||
        every { appSettingsService.isItemCachingEnabled() } returns false
 | 
			
		||||
        every { appSettingsService.isUpdateSourcesEnabled() } returns false
 | 
			
		||||
        coEvery { api.sources() } returns StatusAndData(success = true, data = sources)
 | 
			
		||||
        every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB
 | 
			
		||||
        initializeRepository(MutableStateFlow(false))
 | 
			
		||||
        var testSources: List<SelfossModel.Source>?
 | 
			
		||||
        runBlocking {
 | 
			
		||||
@@ -1302,16 +985,7 @@ class RepositoryTest {
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        initializeRepository()
 | 
			
		||||
        repository.setTagFilter(SelfossModel.Tag("Tag", "read", 0))
 | 
			
		||||
        repository.setSourceFilter(SelfossModel.Source(
 | 
			
		||||
            1,
 | 
			
		||||
            "First source",
 | 
			
		||||
            listOf("Test", "second"),
 | 
			
		||||
            "spouts\\rss\\fulltextrss",
 | 
			
		||||
            "",
 | 
			
		||||
            "d8c92cdb1ef119ea85c4b9205c879ca7.png"
 | 
			
		||||
        ))
 | 
			
		||||
        repository.searchFilter = "search"
 | 
			
		||||
        prepareSearch()
 | 
			
		||||
        runBlocking {
 | 
			
		||||
            repository.tryToCacheItemsAndGetNewOnes()
 | 
			
		||||
        }
 | 
			
		||||
@@ -1325,16 +999,7 @@ class RepositoryTest {
 | 
			
		||||
                StatusAndData(success = false, data = generateTestApiItem())
 | 
			
		||||
 | 
			
		||||
        initializeRepository()
 | 
			
		||||
        repository.setTagFilter(SelfossModel.Tag("Tag", "read", 0))
 | 
			
		||||
        repository.setSourceFilter(SelfossModel.Source(
 | 
			
		||||
            1,
 | 
			
		||||
            "First source",
 | 
			
		||||
            listOf("Test", "second"),
 | 
			
		||||
            "spouts\\rss\\fulltextrss",
 | 
			
		||||
            "",
 | 
			
		||||
            "d8c92cdb1ef119ea85c4b9205c879ca7.png"
 | 
			
		||||
        ))
 | 
			
		||||
        repository.searchFilter = "search"
 | 
			
		||||
        prepareSearch()
 | 
			
		||||
        runBlocking {
 | 
			
		||||
            repository.tryToCacheItemsAndGetNewOnes()
 | 
			
		||||
        }
 | 
			
		||||
@@ -1348,20 +1013,26 @@ class RepositoryTest {
 | 
			
		||||
                StatusAndData(success = false, data = generateTestApiItem())
 | 
			
		||||
 | 
			
		||||
        initializeRepository(MutableStateFlow(false))
 | 
			
		||||
        repository.setTagFilter(SelfossModel.Tag("Tag", "read", 0))
 | 
			
		||||
        repository.setSourceFilter(SelfossModel.Source(
 | 
			
		||||
            1,
 | 
			
		||||
            "First source",
 | 
			
		||||
            listOf("Test", "second"),
 | 
			
		||||
            "spouts\\rss\\fulltextrss",
 | 
			
		||||
            "",
 | 
			
		||||
            "d8c92cdb1ef119ea85c4b9205c879ca7.png"
 | 
			
		||||
        ))
 | 
			
		||||
        repository.searchFilter = "search"
 | 
			
		||||
        prepareSearch()
 | 
			
		||||
        runBlocking {
 | 
			
		||||
            repository.tryToCacheItemsAndGetNewOnes()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        coVerify(exactly = 0) { api.getItems(any(), 0, null, null, null, null, 200) }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun prepareSearch() {
 | 
			
		||||
        repository.setTagFilter(SelfossModel.Tag("Tag", "read", 0))
 | 
			
		||||
        repository.setSourceFilter(
 | 
			
		||||
            SelfossModel.Source(
 | 
			
		||||
                1,
 | 
			
		||||
                "First source",
 | 
			
		||||
                listOf("Test", "second"),
 | 
			
		||||
                "spouts\\rss\\fulltextrss",
 | 
			
		||||
                "",
 | 
			
		||||
                "d8c92cdb1ef119ea85c4b9205c879ca7.png"
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
        repository.searchFilter = "search"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -11,12 +11,10 @@ plugins {
 | 
			
		||||
    id("com.android.library").version("7.3.1").apply(false)
 | 
			
		||||
    kotlin("android").version("1.7.20").apply(false)
 | 
			
		||||
    kotlin("multiplatform").version("1.7.20").apply(false)
 | 
			
		||||
    id("org.sonarqube").version("3.4.0.2513").apply(false)
 | 
			
		||||
    id("com.mikepenz.aboutlibraries.plugin").version("10.5.1").apply(false)
 | 
			
		||||
    id("org.jetbrains.kotlinx.kover") version "0.6.1"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
apply(plugin = "org.sonarqube")
 | 
			
		||||
 | 
			
		||||
allprojects {
 | 
			
		||||
    repositories {
 | 
			
		||||
        google()
 | 
			
		||||
@@ -30,3 +28,7 @@ allprojects {
 | 
			
		||||
tasks.register("clean", Delete::class) {
 | 
			
		||||
    delete(rootProject.buildDir)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
koverMerged {
 | 
			
		||||
    enable()
 | 
			
		||||
}
 | 
			
		||||
@@ -10,6 +10,7 @@ plugins {
 | 
			
		||||
    id("com.android.library")
 | 
			
		||||
    id("com.squareup.sqldelight")
 | 
			
		||||
    kotlin("plugin.serialization") version "1.4.10"
 | 
			
		||||
    id("org.jetbrains.kotlinx.kover") version "0.6.1"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
kotlin {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,6 @@
 | 
			
		||||
package bou.amine.apps.readerforselfossv2.model
 | 
			
		||||
 | 
			
		||||
import bou.amine.apps.readerforselfossv2.utils.DateUtils
 | 
			
		||||
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
 | 
			
		||||
import kotlinx.serialization.KSerializer
 | 
			
		||||
import kotlinx.serialization.Serializable
 | 
			
		||||
import kotlinx.serialization.descriptors.PrimitiveKind
 | 
			
		||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
 | 
			
		||||
import kotlinx.serialization.descriptors.SerialDescriptor
 | 
			
		||||
import kotlinx.serialization.encoding.Decoder
 | 
			
		||||
import kotlinx.serialization.encoding.Encoder
 | 
			
		||||
import kotlinx.serialization.json.*
 | 
			
		||||
 | 
			
		||||
class MercuryModel {
 | 
			
		||||
 | 
			
		||||
@@ -20,138 +11,4 @@ class MercuryModel {
 | 
			
		||||
        val lead_image_url: String?,
 | 
			
		||||
        val url: String
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    @Serializable
 | 
			
		||||
    data class Tag(
 | 
			
		||||
        val tag: String,
 | 
			
		||||
        val color: String,
 | 
			
		||||
        val unread: Int
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    @Serializable
 | 
			
		||||
    class Stats(
 | 
			
		||||
        val total: Int,
 | 
			
		||||
        val unread: Int,
 | 
			
		||||
        val starred: Int
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    @Serializable
 | 
			
		||||
    data class Spout(
 | 
			
		||||
        val name: String,
 | 
			
		||||
        val description: String
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    @Serializable
 | 
			
		||||
    data class ApiVersion(
 | 
			
		||||
        val version: String?,
 | 
			
		||||
        val apiversion: String?
 | 
			
		||||
    ) {
 | 
			
		||||
        fun getApiMajorVersion() : Int {
 | 
			
		||||
            var versionNumber = 0
 | 
			
		||||
            if (apiversion != null) {
 | 
			
		||||
                versionNumber = apiversion.substringBefore(".").toInt()
 | 
			
		||||
            }
 | 
			
		||||
            return versionNumber
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Serializable
 | 
			
		||||
    data class Source(
 | 
			
		||||
        val id: Int,
 | 
			
		||||
        val title: String,
 | 
			
		||||
        @Serializable(with = TagsListSerializer::class)
 | 
			
		||||
        val tags: List<String>,
 | 
			
		||||
        val spout: String,
 | 
			
		||||
        val error: String,
 | 
			
		||||
        val icon: String?
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    @Serializable
 | 
			
		||||
    data class Item(
 | 
			
		||||
        val id: Int,
 | 
			
		||||
        val datetime: String,
 | 
			
		||||
        val title: String,
 | 
			
		||||
        val content: String,
 | 
			
		||||
        @Serializable(with = BooleanSerializer::class)
 | 
			
		||||
        var unread: Boolean,
 | 
			
		||||
        @Serializable(with = BooleanSerializer::class)
 | 
			
		||||
        var starred: Boolean,
 | 
			
		||||
        val thumbnail: String?,
 | 
			
		||||
        val icon: String?,
 | 
			
		||||
        val link: String,
 | 
			
		||||
        val sourcetitle: String,
 | 
			
		||||
        @Serializable(with = TagsListSerializer::class)
 | 
			
		||||
        val tags: List<String>
 | 
			
		||||
    ) {
 | 
			
		||||
        // TODO: maybe find a better way to handle these kind of urls
 | 
			
		||||
        fun getLinkDecoded(): String {
 | 
			
		||||
            var stringUrl: String
 | 
			
		||||
            stringUrl =
 | 
			
		||||
                if (link.startsWith("http://news.google.com/news/") || link.startsWith("https://news.google.com/news/")) {
 | 
			
		||||
                    if (link.contains("&url=")) {
 | 
			
		||||
                        link.substringAfter("&url=")
 | 
			
		||||
                    } else {
 | 
			
		||||
                        this.link.replace("&", "&")
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    this.link.replace("&", "&")
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            // handle :443 => https
 | 
			
		||||
            if (stringUrl.contains(":443")) {
 | 
			
		||||
                stringUrl = stringUrl.replace(":443", "").replace("http://", "https://")
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // handle url not starting with http
 | 
			
		||||
            if (stringUrl.startsWith("//")) {
 | 
			
		||||
                stringUrl = "http:$stringUrl"
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return stringUrl
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fun sourceAndDateText(): String =
 | 
			
		||||
            this.sourcetitle.getHtmlDecoded() + DateUtils.parseRelativeDate(this.datetime)
 | 
			
		||||
 | 
			
		||||
        fun toggleStar(): Item {
 | 
			
		||||
            this.starred = !this.starred
 | 
			
		||||
            return this
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // TODO: this seems to be super slow.
 | 
			
		||||
    object TagsListSerializer : KSerializer<List<String>> {
 | 
			
		||||
        override fun deserialize(decoder: Decoder): List<String> {
 | 
			
		||||
            return when(val json = ((decoder as JsonDecoder).decodeJsonElement())) {
 | 
			
		||||
                is JsonArray -> json.toList().map { it.toString() }
 | 
			
		||||
                else -> json.toString().split(",")
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        override val descriptor: SerialDescriptor
 | 
			
		||||
            get() = PrimitiveSerialDescriptor("tags", PrimitiveKind.STRING)
 | 
			
		||||
 | 
			
		||||
        override fun serialize(encoder: Encoder, value: List<String>) {
 | 
			
		||||
            TODO("Not yet implemented")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    object BooleanSerializer : KSerializer<Boolean> {
 | 
			
		||||
        override fun deserialize(decoder: Decoder): Boolean {
 | 
			
		||||
            val json = ((decoder as JsonDecoder).decodeJsonElement()).jsonPrimitive
 | 
			
		||||
            return if (json.booleanOrNull != null) {
 | 
			
		||||
                json.boolean
 | 
			
		||||
            } else {
 | 
			
		||||
                json.int == 1
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        override val descriptor: SerialDescriptor
 | 
			
		||||
            get() = PrimitiveSerialDescriptor("b", PrimitiveKind.BOOLEAN)
 | 
			
		||||
 | 
			
		||||
        override fun serialize(encoder: Encoder, value: Boolean) {
 | 
			
		||||
            TODO("Not yet implemented")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,5 @@
 | 
			
		||||
package bou.amine.apps.readerforselfossv2.model
 | 
			
		||||
 | 
			
		||||
import io.ktor.client.call.*
 | 
			
		||||
import io.ktor.client.statement.*
 | 
			
		||||
import io.ktor.http.*
 | 
			
		||||
import kotlinx.serialization.Serializable
 | 
			
		||||
 | 
			
		||||
@Serializable
 | 
			
		||||
@@ -21,28 +18,4 @@ class StatusAndData<T>(val success: Boolean, val data: T? = null) {
 | 
			
		||||
            return StatusAndData(false)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
suspend fun responseOrSuccessIf404(r: HttpResponse): SuccessResponse {
 | 
			
		||||
    return if (r.status === HttpStatusCode.NotFound) {
 | 
			
		||||
        SuccessResponse(true)
 | 
			
		||||
    } else {
 | 
			
		||||
        maybeResponse(r)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
suspend fun maybeResponse(r: HttpResponse): SuccessResponse {
 | 
			
		||||
    return if (r.status.isSuccess()) {
 | 
			
		||||
        r.body()
 | 
			
		||||
    } else {
 | 
			
		||||
        SuccessResponse(false)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
suspend inline fun <reified T> bodyOrFailure(r: HttpResponse): StatusAndData<T> {
 | 
			
		||||
    return if (r.status.isSuccess()) {
 | 
			
		||||
        StatusAndData.succes(r.body())
 | 
			
		||||
    } else {
 | 
			
		||||
        StatusAndData.error()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -8,13 +8,19 @@ import bou.amine.apps.readerforselfossv2.rest.SelfossApi
 | 
			
		||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
 | 
			
		||||
import bou.amine.apps.readerforselfossv2.utils.*
 | 
			
		||||
import io.github.aakira.napier.Napier
 | 
			
		||||
import io.ktor.client.call.*
 | 
			
		||||
import kotlinx.coroutines.CoroutineScope
 | 
			
		||||
import kotlinx.coroutines.Dispatchers
 | 
			
		||||
import kotlinx.coroutines.flow.MutableStateFlow
 | 
			
		||||
import kotlinx.coroutines.flow.asStateFlow
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
 | 
			
		||||
class Repository(private val api: SelfossApi, private val appSettingsService: AppSettingsService, val isConnectionAvailable: MutableStateFlow<Boolean>, private val db: ReaderForSelfossDB) {
 | 
			
		||||
class Repository(
 | 
			
		||||
    private val api: SelfossApi,
 | 
			
		||||
    private val appSettingsService: AppSettingsService,
 | 
			
		||||
    val isConnectionAvailable: MutableStateFlow<Boolean>,
 | 
			
		||||
    private val db: ReaderForSelfossDB
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    var items = ArrayList<SelfossModel.Item>()
 | 
			
		||||
    var connectionMonitored = false
 | 
			
		||||
@@ -146,7 +152,8 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    suspend fun getTags(): List<SelfossModel.Tag> {
 | 
			
		||||
        val isDatabaseEnabled = appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled()
 | 
			
		||||
        val isDatabaseEnabled =
 | 
			
		||||
            appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled()
 | 
			
		||||
        return if (isNetworkAvailable() && !fetchedTags) {
 | 
			
		||||
            val apiTags = api.tags()
 | 
			
		||||
            if (apiTags.success && apiTags.data != null && isDatabaseEnabled) {
 | 
			
		||||
@@ -178,7 +185,8 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    suspend fun getSources(): ArrayList<SelfossModel.Source> {
 | 
			
		||||
        val isDatabaseEnabled = appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled()
 | 
			
		||||
        val isDatabaseEnabled =
 | 
			
		||||
            appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled()
 | 
			
		||||
        return if (isNetworkAvailable() && !fetchedSources) {
 | 
			
		||||
            val apiSources = api.sources()
 | 
			
		||||
            if (apiSources.success && apiSources.data != null && isDatabaseEnabled) {
 | 
			
		||||
@@ -380,18 +388,35 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
 | 
			
		||||
        return result
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    suspend fun shouldBeSelfossInstance(): Pair<Boolean, Boolean> {
 | 
			
		||||
        var fetchFailed = true
 | 
			
		||||
        var showSelfossOnlyModal = false
 | 
			
		||||
        if (isNetworkAvailable()) {
 | 
			
		||||
            try {
 | 
			
		||||
                // Trying to fetch one item, and check someone is trying to use the app with
 | 
			
		||||
                // a random rss feed, that would throw a NoTransformationFoundException
 | 
			
		||||
                fetchFailed = !api.getItemsWithoutCatch().success
 | 
			
		||||
            } catch (e: NoTransformationFoundException) {
 | 
			
		||||
                showSelfossOnlyModal = true
 | 
			
		||||
            } catch (e: Throwable) {
 | 
			
		||||
                Napier.e(e.stackTraceToString(), tag = "RepositoryImpl.shouldBeSelfossInstance")
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return Pair(fetchFailed, showSelfossOnlyModal)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    suspend fun logout() {
 | 
			
		||||
        if (isNetworkAvailable()) {
 | 
			
		||||
            try {
 | 
			
		||||
                val response = api.logout()
 | 
			
		||||
                if (response.isSuccess) {
 | 
			
		||||
                if (!response.isSuccess) {
 | 
			
		||||
                    Napier.e("Couldn't logout.", tag = "RepositoryImpl.logout")
 | 
			
		||||
                }
 | 
			
		||||
            } catch (cause: Throwable) {
 | 
			
		||||
                Napier.e(cause.stackTraceToString(), tag = "RepositoryImpl.logout")
 | 
			
		||||
            } finally {
 | 
			
		||||
                appSettingsService.clearAll()
 | 
			
		||||
            }
 | 
			
		||||
            appSettingsService.clearAll()
 | 
			
		||||
        } else {
 | 
			
		||||
            appSettingsService.clearAll()
 | 
			
		||||
        }
 | 
			
		||||
@@ -456,11 +481,29 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
 | 
			
		||||
 | 
			
		||||
    private fun getDBItems(): List<ITEM> = db.itemsQueries.items().executeAsList()
 | 
			
		||||
 | 
			
		||||
    private fun insertDBAction(articleid: String, read: Boolean = false, unread: Boolean = false, starred: Boolean = false, unstarred: Boolean = false) =
 | 
			
		||||
    private fun insertDBAction(
 | 
			
		||||
        articleid: String,
 | 
			
		||||
        read: Boolean = false,
 | 
			
		||||
        unread: Boolean = false,
 | 
			
		||||
        starred: Boolean = false,
 | 
			
		||||
        unstarred: Boolean = false
 | 
			
		||||
    ) =
 | 
			
		||||
        db.actionsQueries.insertAction(articleid, read, unread, starred, unstarred)
 | 
			
		||||
 | 
			
		||||
    private fun updateDBItem(item: SelfossModel.Item) =
 | 
			
		||||
        db.itemsQueries.updateItem(item.datetime, item.title.getHtmlDecoded(), item.content, item.unread, item.starred, item.thumbnail, item.icon, item.link, item.sourcetitle, item.tags.joinToString(","), item.id.toString())
 | 
			
		||||
        db.itemsQueries.updateItem(
 | 
			
		||||
            item.datetime,
 | 
			
		||||
            item.title.getHtmlDecoded(),
 | 
			
		||||
            item.content,
 | 
			
		||||
            item.unread,
 | 
			
		||||
            item.starred,
 | 
			
		||||
            item.thumbnail,
 | 
			
		||||
            item.icon,
 | 
			
		||||
            item.link,
 | 
			
		||||
            item.sourcetitle,
 | 
			
		||||
            item.tags.joinToString(","),
 | 
			
		||||
            item.id.toString()
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    suspend fun tryToCacheItemsAndGetNewOnes(): List<SelfossModel.Item> {
 | 
			
		||||
        try {
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,79 @@
 | 
			
		||||
package bou.amine.apps.readerforselfossv2.rest
 | 
			
		||||
 | 
			
		||||
import bou.amine.apps.readerforselfossv2.model.StatusAndData
 | 
			
		||||
import bou.amine.apps.readerforselfossv2.model.SuccessResponse
 | 
			
		||||
import io.github.aakira.napier.Napier
 | 
			
		||||
import io.ktor.client.*
 | 
			
		||||
import io.ktor.client.call.*
 | 
			
		||||
import io.ktor.client.request.*
 | 
			
		||||
import io.ktor.client.request.forms.*
 | 
			
		||||
import io.ktor.client.statement.*
 | 
			
		||||
import io.ktor.http.*
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
suspend fun responseOrSuccessIf404(r: HttpResponse?): SuccessResponse {
 | 
			
		||||
    return if (r != null && r.status === HttpStatusCode.NotFound) {
 | 
			
		||||
        SuccessResponse(true)
 | 
			
		||||
    } else {
 | 
			
		||||
        maybeResponse(r)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
suspend fun maybeResponse(r: HttpResponse?): SuccessResponse {
 | 
			
		||||
    return if (r != null && r.status.isSuccess()) {
 | 
			
		||||
        r.body()
 | 
			
		||||
    } else {
 | 
			
		||||
        SuccessResponse(false)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
suspend inline fun <reified T> bodyOrFailure(r: HttpResponse?): StatusAndData<T> {
 | 
			
		||||
    return if (r != null && r.status.isSuccess()) {
 | 
			
		||||
        StatusAndData.succes(r.body())
 | 
			
		||||
    } else {
 | 
			
		||||
        StatusAndData.error()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
inline fun tryToRequest(
 | 
			
		||||
    requestType: String,
 | 
			
		||||
    fn: () -> HttpResponse
 | 
			
		||||
): HttpResponse? {
 | 
			
		||||
    var response: HttpResponse? = null
 | 
			
		||||
    try {
 | 
			
		||||
        response = fn()
 | 
			
		||||
    } catch (ex: Exception) {
 | 
			
		||||
        Napier.e("Couldn't execute $requestType request", ex, "tryTo$requestType")
 | 
			
		||||
    }
 | 
			
		||||
    return response
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
suspend inline fun HttpClient.tryToGet(
 | 
			
		||||
    urlString: String,
 | 
			
		||||
    crossinline block: HttpRequestBuilder.() -> Unit = {}
 | 
			
		||||
): HttpResponse? = tryToRequest("Get") { return this.get { url(urlString); block() } }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
suspend inline fun HttpClient.tryToPost(
 | 
			
		||||
    urlString: String,
 | 
			
		||||
    block: HttpRequestBuilder.() -> Unit = {}
 | 
			
		||||
): HttpResponse? = tryToRequest("Post") { return this.post { url(urlString); block() } }
 | 
			
		||||
 | 
			
		||||
suspend inline fun HttpClient.tryToDelete(
 | 
			
		||||
    urlString: String,
 | 
			
		||||
    block: HttpRequestBuilder.() -> Unit = {}
 | 
			
		||||
): HttpResponse? = tryToRequest("Delete") { return this.delete { url(urlString); block() } }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
suspend fun HttpClient.tryToSubmitForm(
 | 
			
		||||
    url: String,
 | 
			
		||||
    formParameters: Parameters = Parameters.Empty,
 | 
			
		||||
    encodeInQuery: Boolean = false,
 | 
			
		||||
    block: HttpRequestBuilder.() -> Unit = {}
 | 
			
		||||
): HttpResponse? =
 | 
			
		||||
    tryToRequest("SubmitForm") {
 | 
			
		||||
        return this.submitForm(formParameters, encodeInQuery) {
 | 
			
		||||
            url(url)
 | 
			
		||||
            block()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -1,6 +1,8 @@
 | 
			
		||||
package bou.amine.apps.readerforselfossv2.rest
 | 
			
		||||
 | 
			
		||||
import bou.amine.apps.readerforselfossv2.model.*
 | 
			
		||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
 | 
			
		||||
import bou.amine.apps.readerforselfossv2.model.StatusAndData
 | 
			
		||||
import bou.amine.apps.readerforselfossv2.model.SuccessResponse
 | 
			
		||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
 | 
			
		||||
import io.github.aakira.napier.Napier
 | 
			
		||||
import io.ktor.client.*
 | 
			
		||||
@@ -10,7 +12,6 @@ import io.ktor.client.plugins.contentnegotiation.*
 | 
			
		||||
import io.ktor.client.plugins.cookies.*
 | 
			
		||||
import io.ktor.client.plugins.logging.*
 | 
			
		||||
import io.ktor.client.request.*
 | 
			
		||||
import io.ktor.client.request.forms.*
 | 
			
		||||
import io.ktor.client.statement.*
 | 
			
		||||
import io.ktor.http.*
 | 
			
		||||
import io.ktor.serialization.kotlinx.json.*
 | 
			
		||||
@@ -46,11 +47,11 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
            }
 | 
			
		||||
            install(HttpCookies)
 | 
			
		||||
            install(HttpRequestRetry) {
 | 
			
		||||
                maxRetries = 2
 | 
			
		||||
                maxRetries = 1
 | 
			
		||||
                retryIf { _, response ->
 | 
			
		||||
                    response.status == HttpStatusCode.Forbidden && shouldHavePostLogin() && hasLoginInfo()
 | 
			
		||||
                }
 | 
			
		||||
                modifyRequest { _ ->
 | 
			
		||||
                modifyRequest {
 | 
			
		||||
                    Napier.i("Will modify", tag = "HttpSend")
 | 
			
		||||
                    CoroutineScope(Dispatchers.Main).launch {
 | 
			
		||||
                        Napier.i("Will login", tag = "HttpSend")
 | 
			
		||||
@@ -75,7 +76,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
 | 
			
		||||
    // 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() = appSettingsService.getUserName() != null && appSettingsService.getPassword() != null
 | 
			
		||||
    private fun hasLoginInfo() = appSettingsService.getUserName().isNotEmpty() && appSettingsService.getPassword().isNotEmpty()
 | 
			
		||||
 | 
			
		||||
    suspend fun login(): SuccessResponse =
 | 
			
		||||
        if (appSettingsService.getUserName().isNotEmpty() && appSettingsService.getPassword().isNotEmpty()) {
 | 
			
		||||
@@ -88,12 +89,12 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
            SuccessResponse(true)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    private suspend fun getLogin() = maybeResponse(client.get(url("/login")) {
 | 
			
		||||
    private suspend fun getLogin() = maybeResponse(client.tryToGet(url("/login")) {
 | 
			
		||||
        parameter("username", appSettingsService.getUserName())
 | 
			
		||||
        parameter("password", appSettingsService.getPassword())
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    private suspend fun postLogin() = maybeResponse(client.post(url("/login")) {
 | 
			
		||||
    private suspend fun postLogin() = maybeResponse(client.tryToPost(url("/login")) {
 | 
			
		||||
        parameter("username", appSettingsService.getUserName())
 | 
			
		||||
        parameter("password", appSettingsService.getPassword())
 | 
			
		||||
    })
 | 
			
		||||
@@ -106,9 +107,9 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
            maybeLogoutIfAvailable()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    private suspend fun maybeLogoutIfAvailable() = responseOrSuccessIf404(client.get(url("/logout")))
 | 
			
		||||
    private suspend fun maybeLogoutIfAvailable() = responseOrSuccessIf404(client.tryToGet(url("/logout")))
 | 
			
		||||
 | 
			
		||||
    private suspend fun doLogout() = maybeResponse(client.delete(url("/api/session/current")))
 | 
			
		||||
    private suspend fun doLogout() = maybeResponse(client.tryToDelete(url("/api/session/current")))
 | 
			
		||||
 | 
			
		||||
    suspend fun getItems(
 | 
			
		||||
        type: String,
 | 
			
		||||
@@ -119,7 +120,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
        updatedSince: String?,
 | 
			
		||||
        items: Int? = null
 | 
			
		||||
    ): StatusAndData<List<SelfossModel.Item>> =
 | 
			
		||||
        bodyOrFailure(client.get(url("/items")) {
 | 
			
		||||
        bodyOrFailure(client.tryToGet(url("/items")) {
 | 
			
		||||
            if (!shouldHavePostLogin()) {
 | 
			
		||||
                parameter("username", appSettingsService.getUserName())
 | 
			
		||||
                parameter("password", appSettingsService.getPassword())
 | 
			
		||||
@@ -133,8 +134,18 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
            parameter("offset", offset)
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    suspend fun getItemsWithoutCatch(): StatusAndData<List<SelfossModel.Item>> =
 | 
			
		||||
        bodyOrFailure(client.get(url("/items")) {
 | 
			
		||||
            if (!shouldHavePostLogin()) {
 | 
			
		||||
                parameter("username", appSettingsService.getUserName())
 | 
			
		||||
                parameter("password", appSettingsService.getPassword())
 | 
			
		||||
            }
 | 
			
		||||
            parameter("type", "all")
 | 
			
		||||
            parameter("items", 1)
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    suspend fun stats(): StatusAndData<SelfossModel.Stats> =
 | 
			
		||||
        bodyOrFailure(client.get(url("/stats")) {
 | 
			
		||||
        bodyOrFailure(client.tryToGet(url("/stats")) {
 | 
			
		||||
            if (!shouldHavePostLogin()) {
 | 
			
		||||
                parameter("username", appSettingsService.getUserName())
 | 
			
		||||
                parameter("password", appSettingsService.getPassword())
 | 
			
		||||
@@ -142,7 +153,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    suspend fun tags(): StatusAndData<List<SelfossModel.Tag>> =
 | 
			
		||||
        bodyOrFailure(client.get(url("/tags")) {
 | 
			
		||||
        bodyOrFailure(client.tryToGet(url("/tags")) {
 | 
			
		||||
            if (!shouldHavePostLogin()) {
 | 
			
		||||
                parameter("username", appSettingsService.getUserName())
 | 
			
		||||
                parameter("password", appSettingsService.getPassword())
 | 
			
		||||
@@ -150,7 +161,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    suspend fun update(): StatusAndData<String> =
 | 
			
		||||
        bodyOrFailure(client.get(url("/update")) {
 | 
			
		||||
        bodyOrFailure(client.tryToGet(url("/update")) {
 | 
			
		||||
            if (!shouldHavePostLogin()) {
 | 
			
		||||
                parameter("username", appSettingsService.getUserName())
 | 
			
		||||
                parameter("password", appSettingsService.getPassword())
 | 
			
		||||
@@ -158,7 +169,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    suspend fun spouts(): StatusAndData<Map<String, SelfossModel.Spout>> =
 | 
			
		||||
        bodyOrFailure(client.get(url("/sources/spouts")) {
 | 
			
		||||
        bodyOrFailure(client.tryToGet(url("/sources/spouts")) {
 | 
			
		||||
            if (!shouldHavePostLogin()) {
 | 
			
		||||
                parameter("username", appSettingsService.getUserName())
 | 
			
		||||
                parameter("password", appSettingsService.getPassword())
 | 
			
		||||
@@ -166,7 +177,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    suspend fun sources(): StatusAndData<ArrayList<SelfossModel.Source>> =
 | 
			
		||||
        bodyOrFailure(client.get(url("/sources/list")) {
 | 
			
		||||
        bodyOrFailure(client.tryToGet(url("/sources/list")) {
 | 
			
		||||
            if (!shouldHavePostLogin()) {
 | 
			
		||||
                parameter("username", appSettingsService.getUserName())
 | 
			
		||||
                parameter("password", appSettingsService.getPassword())
 | 
			
		||||
@@ -174,10 +185,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    suspend fun version(): StatusAndData<SelfossModel.ApiVersion> =
 | 
			
		||||
        bodyOrFailure(client.get(url("/api/about")))
 | 
			
		||||
        bodyOrFailure(client.tryToGet(url("/api/about")))
 | 
			
		||||
 | 
			
		||||
    suspend fun markAsRead(id: String): SuccessResponse =
 | 
			
		||||
        maybeResponse(client.post(url("/mark/$id")) {
 | 
			
		||||
        maybeResponse(client.tryToPost(url("/mark/$id")) {
 | 
			
		||||
            if (!shouldHavePostLogin()) {
 | 
			
		||||
                parameter("username", appSettingsService.getUserName())
 | 
			
		||||
                parameter("password", appSettingsService.getPassword())
 | 
			
		||||
@@ -185,7 +196,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    suspend fun unmarkAsRead(id: String): SuccessResponse =
 | 
			
		||||
        maybeResponse(client.post(url("/unmark/$id")) {
 | 
			
		||||
        maybeResponse(client.tryToPost(url("/unmark/$id")) {
 | 
			
		||||
            if (!shouldHavePostLogin()) {
 | 
			
		||||
                parameter("username", appSettingsService.getUserName())
 | 
			
		||||
                parameter("password", appSettingsService.getPassword())
 | 
			
		||||
@@ -193,7 +204,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    suspend fun starr(id: String): SuccessResponse =
 | 
			
		||||
        maybeResponse(client.post(url("/starr/$id")) {
 | 
			
		||||
        maybeResponse(client.tryToPost(url("/starr/$id")) {
 | 
			
		||||
            if (!shouldHavePostLogin()) {
 | 
			
		||||
                parameter("username", appSettingsService.getUserName())
 | 
			
		||||
                parameter("password", appSettingsService.getPassword())
 | 
			
		||||
@@ -201,7 +212,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    suspend fun unstarr(id: String): SuccessResponse =
 | 
			
		||||
        maybeResponse(client.post(url("/unstarr/$id")) {
 | 
			
		||||
        maybeResponse(client.tryToPost(url("/unstarr/$id")) {
 | 
			
		||||
            if (!shouldHavePostLogin()) {
 | 
			
		||||
                parameter("username", appSettingsService.getUserName())
 | 
			
		||||
                parameter("password", appSettingsService.getPassword())
 | 
			
		||||
@@ -209,7 +220,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    suspend fun markAllAsRead(ids: List<String>): SuccessResponse =
 | 
			
		||||
        maybeResponse(client.submitForm(
 | 
			
		||||
        maybeResponse(client.tryToSubmitForm(
 | 
			
		||||
            url = url("/mark"),
 | 
			
		||||
            formParameters = Parameters.build {
 | 
			
		||||
                if (!shouldHavePostLogin()) {
 | 
			
		||||
@@ -229,43 +240,21 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
    ): SuccessResponse =
 | 
			
		||||
        maybeResponse(
 | 
			
		||||
            if (appSettingsService.getApiVersion() > 1) {
 | 
			
		||||
                createSource2(title, url, spout, tags, filter)
 | 
			
		||||
                createSource("tags[]", title, url, spout, tags, filter)
 | 
			
		||||
            } else {
 | 
			
		||||
                createSource(title, url, spout, tags, filter)
 | 
			
		||||
                createSource("tags", title, url, spout, tags, filter)
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    private suspend fun createSource(
 | 
			
		||||
        tagsParamName: String,
 | 
			
		||||
        title: String,
 | 
			
		||||
        url: String,
 | 
			
		||||
        spout: String,
 | 
			
		||||
        tags: String,
 | 
			
		||||
        filter: String
 | 
			
		||||
    ): HttpResponse =
 | 
			
		||||
        client.submitForm(
 | 
			
		||||
            url = url("/source"),
 | 
			
		||||
            formParameters = Parameters.build {
 | 
			
		||||
                // TODO: test this
 | 
			
		||||
                if (!shouldHavePostLogin()) {
 | 
			
		||||
                    append("username", appSettingsService.getUserName())
 | 
			
		||||
                    append("password", appSettingsService.getPassword())
 | 
			
		||||
                }
 | 
			
		||||
                append("title", title)
 | 
			
		||||
                append("url", url)
 | 
			
		||||
                append("spout", spout)
 | 
			
		||||
                append("tags", tags)
 | 
			
		||||
                append("filter", filter)
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    private suspend fun createSource2(
 | 
			
		||||
        title: String,
 | 
			
		||||
        url: String,
 | 
			
		||||
        spout: String,
 | 
			
		||||
        tags: String,
 | 
			
		||||
        filter: String
 | 
			
		||||
    ): HttpResponse =
 | 
			
		||||
        client.submitForm(
 | 
			
		||||
    ): HttpResponse? =
 | 
			
		||||
        client.tryToSubmitForm(
 | 
			
		||||
            url = url("/source"),
 | 
			
		||||
            formParameters = Parameters.build {
 | 
			
		||||
                if (!shouldHavePostLogin()) {
 | 
			
		||||
@@ -275,13 +264,13 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
                append("title", title)
 | 
			
		||||
                append("url", url)
 | 
			
		||||
                append("spout", spout)
 | 
			
		||||
                append("tags[]", tags)
 | 
			
		||||
                append(tagsParamName, tags)
 | 
			
		||||
                append("filter", filter)
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    suspend fun deleteSource(id: Int): SuccessResponse =
 | 
			
		||||
        maybeResponse(client.delete(url("/source/$id")) {
 | 
			
		||||
        maybeResponse(client.tryToDelete(url("/source/$id")) {
 | 
			
		||||
            if (!shouldHavePostLogin()) {
 | 
			
		||||
                parameter("username", appSettingsService.getUserName())
 | 
			
		||||
                parameter("password", appSettingsService.getPassword())
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,6 @@ class AppSettingsService {
 | 
			
		||||
    private var _staticBar: Boolean? = null
 | 
			
		||||
    private var _font: String = ""
 | 
			
		||||
    private var _theme: Int? = null
 | 
			
		||||
    private var _enableAnalytics: Boolean? = null
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
@@ -84,7 +83,13 @@ class AppSettingsService {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun refreshItemsNumber() {
 | 
			
		||||
        _itemsNumber = settings.getString(API_ITEMS_NUMBER, "20").toInt()
 | 
			
		||||
        _itemsNumber = try {
 | 
			
		||||
            settings.getString(API_ITEMS_NUMBER, "20").toInt()
 | 
			
		||||
        } catch (e: Exception) {
 | 
			
		||||
            settings.remove(API_ITEMS_NUMBER)
 | 
			
		||||
            20
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun getApiTimeout(): Long {
 | 
			
		||||
@@ -94,9 +99,21 @@ class AppSettingsService {
 | 
			
		||||
        return _apiTimeout!!
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun secToMs(n: Long) = n * 1000
 | 
			
		||||
 | 
			
		||||
    private fun refreshApiTimeout() {
 | 
			
		||||
        val settingsTimeout = settings.getLong(API_TIMEOUT, HttpTimeout.INFINITE_TIMEOUT_MS)
 | 
			
		||||
        _apiTimeout = if (settingsTimeout > 0) settingsTimeout else HttpTimeout.INFINITE_TIMEOUT_MS
 | 
			
		||||
        _apiTimeout = secToMs(try {
 | 
			
		||||
            val settingsTimeout = settings.getString(API_TIMEOUT, "60")
 | 
			
		||||
            if (settingsTimeout.toLong() > 0) {
 | 
			
		||||
                settingsTimeout.toLong()
 | 
			
		||||
            } else {
 | 
			
		||||
                settings.remove(API_TIMEOUT)
 | 
			
		||||
                60
 | 
			
		||||
            }
 | 
			
		||||
        } catch (e: Exception) {
 | 
			
		||||
            settings.remove(API_TIMEOUT)
 | 
			
		||||
            60
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun refreshBaseUrl() {
 | 
			
		||||
@@ -291,17 +308,6 @@ class AppSettingsService {
 | 
			
		||||
        return _staticBar == true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun refreshAnalyticsEnabled() {
 | 
			
		||||
        _enableAnalytics = settings.getBoolean(ENABLE_ANALYTICS, true)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun isAnalyticsEnabled(): Boolean {
 | 
			
		||||
        if (_enableAnalytics != null) {
 | 
			
		||||
            refreshAnalyticsEnabled()
 | 
			
		||||
        }
 | 
			
		||||
        return _enableAnalytics == true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun refreshFont() {
 | 
			
		||||
        _font = settings.getString(READER_FONT, "")
 | 
			
		||||
    }
 | 
			
		||||
@@ -352,7 +358,6 @@ class AppSettingsService {
 | 
			
		||||
        refreshFont()
 | 
			
		||||
        refreshStaticBarEnabled()
 | 
			
		||||
        refreshCurrentTheme()
 | 
			
		||||
        refreshAnalyticsEnabled()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun refreshLoginInformation(
 | 
			
		||||
@@ -453,6 +458,5 @@ class AppSettingsService {
 | 
			
		||||
 | 
			
		||||
        const val CURRENT_THEME = "currentMode"
 | 
			
		||||
 | 
			
		||||
        const val ENABLE_ANALYTICS = "enable_analytics"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										5
									
								
								sonar-project.properties
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								sonar-project.properties
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
sonar.projectKey=RFS2
 | 
			
		||||
sonar.coverage.jacoco.xmlReportPaths=build/reports/kover/merged/xml/report.xml
 | 
			
		||||
sonar.sourceEncoding=UTF-8
 | 
			
		||||
sonar.sources=.
 | 
			
		||||
sonar.exclusions=shared/src/iosArm64Main/**, shared/src/iosX64Main/**, docs/** 
 | 
			
		||||
		Reference in New Issue
	
	Block a user