forked from Louvorg/ReaderForSelfoss-multiplatform
		
	Compare commits
	
		
			9 Commits
		
	
	
		
			v122113172
			...
			v122123351
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					07e3710d44 | ||
| 
						 | 
					e68da7764f | ||
| 
						 | 
					c3ff894027 | ||
| 
						 | 
					f09f731d30 | ||
| 
						 | 
					956c4341c7 | ||
| 
						 | 
					7b68264dd7 | ||
| 
						 | 
					cfcf030bf8 | ||
| 
						 | 
					0e7d7a5835 | ||
| 
						 | 
					0856ebb889 | 
							
								
								
									
										12
									
								
								.drone.yml
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								.drone.yml
									
									
									
									
									
								
							@@ -8,7 +8,7 @@ steps:
 | 
			
		||||
    commands:
 | 
			
		||||
      - echo "---------------------------------------------------------"
 | 
			
		||||
      - echo "Configure gradle..."
 | 
			
		||||
      - mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=true\npushCache=false\nmatomoUrl=\"$MATOMO_URL\"\nmatomoSite=\"$MATOMO_SITE\"\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=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
 | 
			
		||||
@@ -24,10 +24,6 @@ steps:
 | 
			
		||||
        from_secret: sonarScannerHostUrl
 | 
			
		||||
      SONAR_LOGIN:
 | 
			
		||||
        from_secret: sonarScannerLogin
 | 
			
		||||
      MATOMO_URL:
 | 
			
		||||
        from_secret: matomoUrl
 | 
			
		||||
      MATOMO_SITE:
 | 
			
		||||
        from_secret: matomoSite
 | 
			
		||||
trigger:
 | 
			
		||||
  event:
 | 
			
		||||
    - push
 | 
			
		||||
@@ -94,7 +90,7 @@ steps:
 | 
			
		||||
    commands:
 | 
			
		||||
      - echo "---------------------------------------------------------"
 | 
			
		||||
      - echo "Configure gradle..."
 | 
			
		||||
      - mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=true\nmatomoUrl=\"$MATOMO_URL\"\nmatomoSite=\"$MATOMO_SITE\"\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=true\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
 | 
			
		||||
@@ -115,10 +111,6 @@ steps:
 | 
			
		||||
        from_secret: keyPass
 | 
			
		||||
      YOUR_KEY_ALIAS:
 | 
			
		||||
        from_secret: keyAlias
 | 
			
		||||
      MATOMO_URL:
 | 
			
		||||
        from_secret: matomoUrl
 | 
			
		||||
      MATOMO_SITE:
 | 
			
		||||
        from_secret: matomoSite
 | 
			
		||||
 | 
			
		||||
  - name: gitea_release
 | 
			
		||||
    image: plugins/gitea-release
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										24
									
								
								.github/CONTRIBUTING.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										24
									
								
								.github/CONTRIBUTING.md
									
									
									
									
										vendored
									
									
								
							@@ -46,27 +46,3 @@ Always check if the web version of your instance is working.
 | 
			
		||||
I won't provide any selfoss instance url. If you want to help, but to not have one, you'll have to install one, and use it.
 | 
			
		||||
 | 
			
		||||
All the details to need are [here](https://selfoss.aditu.de/).
 | 
			
		||||
 | 
			
		||||
# Build the project
 | 
			
		||||
 | 
			
		||||
You can directly import this project into IntellIJ/Android Studio.
 | 
			
		||||
 | 
			
		||||
You'll have to:
 | 
			
		||||
 | 
			
		||||
- Define some parameters either in `~/.gradle/gradle.properties` or as gradle parameters (see the examples)
 | 
			
		||||
 | 
			
		||||
    - matomoUrl and matomoSite: url and siteId for a matomo instance
 | 
			
		||||
 | 
			
		||||
### Examples:
 | 
			
		||||
#### Inside ~/.gradle/gradle.properties
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
matomoUrl="URL" # It can be empty.
 | 
			
		||||
matomoSite="1" # It can be empty, but needs to be an integer
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### As gradle parameters
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
./gradlew .... -P matomoUrl="URL" -P matomoSite="1"
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
import java.io.ByteArrayOutputStream
 | 
			
		||||
 | 
			
		||||
val ignoreGitVersion: String by project
 | 
			
		||||
val acraVersion = "5.9.7"
 | 
			
		||||
 | 
			
		||||
plugins {
 | 
			
		||||
    id("com.android.application")
 | 
			
		||||
@@ -83,9 +84,6 @@ android {
 | 
			
		||||
 | 
			
		||||
        // tests
 | 
			
		||||
        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
 | 
			
		||||
 | 
			
		||||
        buildConfigField("String", "MATOMO_URL", properties["matomoUrl"] as String)
 | 
			
		||||
        buildConfigField("String", "MATOMO_SITE", properties["matomoSite"] as String)
 | 
			
		||||
    }
 | 
			
		||||
    packagingOptions {
 | 
			
		||||
        resources {
 | 
			
		||||
@@ -190,6 +188,9 @@ dependencies {
 | 
			
		||||
    testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")
 | 
			
		||||
    implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
 | 
			
		||||
 | 
			
		||||
    implementation("ch.acra:acra-http:$acraVersion")
 | 
			
		||||
    implementation("ch.acra:acra-toast:$acraVersion")
 | 
			
		||||
 | 
			
		||||
    // Matomo
 | 
			
		||||
    implementation("com.github.matomo-org:matomo-sdk-android:4.1.4")
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -59,12 +59,15 @@ import com.mikepenz.materialdrawer.util.updateBadge
 | 
			
		||||
import kotlinx.coroutines.CoroutineScope
 | 
			
		||||
import kotlinx.coroutines.Dispatchers
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
import org.acra.ACRA
 | 
			
		||||
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.DimensionQueue
 | 
			
		||||
import org.matomo.sdk.extra.TrackHelper
 | 
			
		||||
import java.security.MessageDigest
 | 
			
		||||
import java.util.concurrent.TimeUnit
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -307,6 +310,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
 | 
			
		||||
 | 
			
		||||
        handleBottomBarActions()
 | 
			
		||||
 | 
			
		||||
        handleGDPRDialog(appSettingsService.settings.getBoolean("GDPR_shown", false))
 | 
			
		||||
 | 
			
		||||
        handleRecurringTask()
 | 
			
		||||
 | 
			
		||||
        CoroutineScope(Dispatchers.Main).launch {
 | 
			
		||||
@@ -316,6 +321,26 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
 | 
			
		||||
        getElementsAccordingToTab()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private fun handleGDPRDialog(GDPRShown: Boolean) {
 | 
			
		||||
        val messageDigest: MessageDigest = MessageDigest.getInstance("SHA-256")
 | 
			
		||||
        messageDigest.update(appSettingsService.getBaseUrl().toByteArray())
 | 
			
		||||
        ACRA.errorReporter.putCustomData("unique_id", String(messageDigest.digest()))
 | 
			
		||||
        if (!GDPRShown) {
 | 
			
		||||
            val alertDialog = AlertDialog.Builder(this).create()
 | 
			
		||||
            alertDialog.setTitle(getString(R.string.gdpr_dialog_title))
 | 
			
		||||
            alertDialog.setMessage(getString(R.string.gdpr_dialog_message))
 | 
			
		||||
            alertDialog.setButton(
 | 
			
		||||
                AlertDialog.BUTTON_NEUTRAL,
 | 
			
		||||
                "OK"
 | 
			
		||||
            ) { dialog, _ ->
 | 
			
		||||
                appSettingsService.settings.putBoolean("GDPR_shown", true)
 | 
			
		||||
                dialog.dismiss()
 | 
			
		||||
            }
 | 
			
		||||
            alertDialog.show()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun initDrawer() {
 | 
			
		||||
        DrawerImageLoader.init(object : AbstractDrawerImageLoader() {
 | 
			
		||||
            override fun set(imageView: ImageView, uri: Uri, placeholder: Drawable, tag: String?) {
 | 
			
		||||
@@ -488,6 +513,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
 | 
			
		||||
        val gdColor = try {
 | 
			
		||||
            Color.parseColor(it.color)
 | 
			
		||||
        } catch (e: IllegalArgumentException) {
 | 
			
		||||
            e.sendSilentlyWithAcra()
 | 
			
		||||
            resources.getColor(R.color.colorPrimary)
 | 
			
		||||
        }
 | 
			
		||||
        gd.setColor(gdColor)
 | 
			
		||||
@@ -870,7 +896,9 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
 | 
			
		||||
                return true
 | 
			
		||||
            }
 | 
			
		||||
            R.id.action_disconnect -> {
 | 
			
		||||
                appSettingsService.clearAll()
 | 
			
		||||
                CoroutineScope(Dispatchers.Main).launch {
 | 
			
		||||
                    repository.logout()
 | 
			
		||||
                }
 | 
			
		||||
                val intent = Intent(this, LoginActivity::class.java)
 | 
			
		||||
                this.startActivity(intent)
 | 
			
		||||
                this@HomeActivity.finish()
 | 
			
		||||
 
 | 
			
		||||
@@ -29,6 +29,13 @@ import kotlinx.coroutines.CoroutineScope
 | 
			
		||||
import kotlinx.coroutines.Dispatchers
 | 
			
		||||
import kotlinx.coroutines.flow.MutableStateFlow
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
import org.acra.ACRA
 | 
			
		||||
import org.acra.ReportField
 | 
			
		||||
import org.acra.config.httpSender
 | 
			
		||||
import org.acra.config.toast
 | 
			
		||||
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
 | 
			
		||||
@@ -43,7 +50,7 @@ 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(BuildConfig.MATOMO_URL, BuildConfig.MATOMO_SITE.toInt()).build(
 | 
			
		||||
        bind<Tracker>() with singleton { TrackerBuilder.createDefault("https://matomo.amine-louveau.fr/matomo.php", if (BuildConfig.DEBUG) 4 else 5).build(
 | 
			
		||||
            Matomo.getInstance(applicationContext)) }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -51,7 +58,6 @@ class MyApp : MultiDexApplication(), DIAware {
 | 
			
		||||
    private val viewModel: AppViewModel by instance()
 | 
			
		||||
    private val connectivityStatus: ConnectivityStatus by instance()
 | 
			
		||||
    private val driverFactory: DriverFactory by instance()
 | 
			
		||||
    private val tracker: Tracker by instance()
 | 
			
		||||
 | 
			
		||||
    // TODO: handle with the "previous" way
 | 
			
		||||
    private val isConnectionAvailable: MutableStateFlow<Boolean> = MutableStateFlow(true)
 | 
			
		||||
@@ -60,28 +66,57 @@ class MyApp : MultiDexApplication(), DIAware {
 | 
			
		||||
        super.onCreate()
 | 
			
		||||
        Napier.base(DebugAntilog())
 | 
			
		||||
 | 
			
		||||
        initDrawerImageLoader()
 | 
			
		||||
        if (!ACRA.isACRASenderServiceProcess()) {
 | 
			
		||||
            initDrawerImageLoader()
 | 
			
		||||
 | 
			
		||||
        tryToHandleBug()
 | 
			
		||||
            tryToHandleBug()
 | 
			
		||||
 | 
			
		||||
        handleNotificationChannels()
 | 
			
		||||
            handleNotificationChannels()
 | 
			
		||||
 | 
			
		||||
        ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifeCycleObserver(connectivityStatus, repository))
 | 
			
		||||
            ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifeCycleObserver(connectivityStatus, repository))
 | 
			
		||||
 | 
			
		||||
        CoroutineScope(Dispatchers.Main).launch {
 | 
			
		||||
            viewModel.networkAvailableProvider.collect { networkAvailable ->
 | 
			
		||||
                val toastMessage = if (networkAvailable) {
 | 
			
		||||
                    repository.handleDBActions()
 | 
			
		||||
                    R.string.network_connectivity_retrieved
 | 
			
		||||
                } else {
 | 
			
		||||
                    R.string.network_connectivity_lost
 | 
			
		||||
            CoroutineScope(Dispatchers.Main).launch {
 | 
			
		||||
                viewModel.networkAvailableProvider.collect { networkAvailable ->
 | 
			
		||||
                    val toastMessage = if (networkAvailable) {
 | 
			
		||||
                        repository.handleDBActions()
 | 
			
		||||
                        R.string.network_connectivity_retrieved
 | 
			
		||||
                    } else {
 | 
			
		||||
                        R.string.network_connectivity_lost
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    Toast.makeText(
 | 
			
		||||
                        applicationContext,
 | 
			
		||||
                        toastMessage,
 | 
			
		||||
                        Toast.LENGTH_SHORT
 | 
			
		||||
                    ).show()
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
                Toast.makeText(
 | 
			
		||||
                    applicationContext,
 | 
			
		||||
                    toastMessage,
 | 
			
		||||
                    Toast.LENGTH_SHORT
 | 
			
		||||
                ).show()
 | 
			
		||||
    override fun attachBaseContext(base: Context?) {
 | 
			
		||||
        super.attachBaseContext(base)
 | 
			
		||||
 | 
			
		||||
        initAcra {
 | 
			
		||||
            reportFormat = StringFormat.JSON
 | 
			
		||||
            reportContent = listOf(
 | 
			
		||||
                ReportField.REPORT_ID, ReportField.INSTALLATION_ID,
 | 
			
		||||
                ReportField.APP_VERSION_CODE, ReportField.APP_VERSION_NAME,
 | 
			
		||||
                ReportField.BUILD, ReportField.ANDROID_VERSION, ReportField.BRAND, ReportField.PHONE_MODEL,
 | 
			
		||||
                ReportField.AVAILABLE_MEM_SIZE, ReportField.TOTAL_MEM_SIZE,
 | 
			
		||||
                ReportField.STACK_TRACE, ReportField.APPLICATION_LOG, ReportField.LOGCAT,
 | 
			
		||||
                ReportField.INITIAL_CONFIGURATION, ReportField.CRASH_CONFIGURATION, ReportField.IS_SILENT,
 | 
			
		||||
                ReportField.USER_APP_START_DATE, ReportField.USER_COMMENT, ReportField.USER_CRASH_DATE, ReportField.USER_EMAIL, ReportField.CUSTOM_DATA)
 | 
			
		||||
            toast {
 | 
			
		||||
                //required
 | 
			
		||||
                text = getString(R.string.crash_toast_text)
 | 
			
		||||
                length = Toast.LENGTH_SHORT
 | 
			
		||||
            }
 | 
			
		||||
            httpSender {
 | 
			
		||||
                uri = "https://bugs.amine-louveau.fr/report" /*best guess, you may need to adjust this*/
 | 
			
		||||
                basicAuthLogin = "LMTlLZuazADohTCm"
 | 
			
		||||
                basicAuthPassword = "he6ghHp83F0PYPfh"
 | 
			
		||||
                httpMethod = HttpSender.Method.POST
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -44,6 +44,8 @@ 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
 | 
			
		||||
@@ -112,6 +114,7 @@ class ArticleFragment : Fragment(), DIAware {
 | 
			
		||||
                typeface = try {
 | 
			
		||||
                    ResourcesCompat.getFont(requireContext(), resId)!!
 | 
			
		||||
                } catch (e: java.lang.Exception) {
 | 
			
		||||
                    e.sendSilentlyWithAcra()
 | 
			
		||||
                    // Just to be sure
 | 
			
		||||
                    null
 | 
			
		||||
                }
 | 
			
		||||
@@ -217,6 +220,7 @@ class ArticleFragment : Fragment(), DIAware {
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        } catch (e: InflateException) {
 | 
			
		||||
            e.sendSilentlyWithAcra()
 | 
			
		||||
            AlertDialog.Builder(requireContext())
 | 
			
		||||
                .setMessage(requireContext().getString(R.string.webview_dialog_issue_message))
 | 
			
		||||
                .setTitle(requireContext().getString(R.string.webview_dialog_issue_title))
 | 
			
		||||
@@ -264,15 +268,18 @@ class ArticleFragment : Fragment(), DIAware {
 | 
			
		||||
                                    URL(response.data!!.url)
 | 
			
		||||
                                    url = response.data!!.url
 | 
			
		||||
                                } catch (e: MalformedURLException) {
 | 
			
		||||
                                    // Mercury returned a relative url. We do nothing.
 | 
			
		||||
                                    // Mercury returned a relative url
 | 
			
		||||
                                    e.sendSilentlyWithAcra()
 | 
			
		||||
                                }
 | 
			
		||||
                            } catch (e: Exception) {
 | 
			
		||||
                                e.sendSilentlyWithAcra()
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            try {
 | 
			
		||||
                                contentText = response.data!!.content.orEmpty()
 | 
			
		||||
                                htmlToWebview()
 | 
			
		||||
                            } catch (e: Exception) {
 | 
			
		||||
                                e.sendSilentlyWithAcra()
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            try {
 | 
			
		||||
@@ -288,13 +295,13 @@ class ArticleFragment : Fragment(), DIAware {
 | 
			
		||||
                                            .apply(RequestOptions.fitCenterTransform())
 | 
			
		||||
                                            .into(binding.imageView)
 | 
			
		||||
                                    } catch (e: Exception) {
 | 
			
		||||
                                        e.sendSilentlyWithAcra()
 | 
			
		||||
                                    }
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    binding.imageView.visibility = View.GONE
 | 
			
		||||
                                }
 | 
			
		||||
                            } catch (e: Exception) {
 | 
			
		||||
                                if (context != null) {
 | 
			
		||||
                                }
 | 
			
		||||
                                e.sendSilentlyWithAcra()
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            try {
 | 
			
		||||
@@ -302,20 +309,17 @@ class ArticleFragment : Fragment(), DIAware {
 | 
			
		||||
 | 
			
		||||
                                binding.progressBar.visibility = View.GONE
 | 
			
		||||
                            } catch (e: Exception) {
 | 
			
		||||
                                if (context != null) {
 | 
			
		||||
                                }
 | 
			
		||||
                                e.sendSilentlyWithAcra()
 | 
			
		||||
                            }
 | 
			
		||||
                        } else {
 | 
			
		||||
                            try {
 | 
			
		||||
                                openInBrowserAfterFailing()
 | 
			
		||||
                            } catch (e: Exception) {
 | 
			
		||||
                                if (context != null) {
 | 
			
		||||
                                }
 | 
			
		||||
                                e.sendSilentlyWithAcra()
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    } catch (e: Exception) {
 | 
			
		||||
                        if (context != null) {
 | 
			
		||||
                        }
 | 
			
		||||
                        e.sendSilentlyWithAcra()
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    openInBrowserAfterFailing()
 | 
			
		||||
@@ -359,19 +363,25 @@ class ArticleFragment : Fragment(), DIAware {
 | 
			
		||||
                    try {
 | 
			
		||||
                        val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
 | 
			
		||||
                        return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.JPEG))
 | 
			
		||||
                    }catch ( e : ExecutionException) {}
 | 
			
		||||
                    } catch ( e : ExecutionException) {
 | 
			
		||||
                        e.sendSilentlyWithAcra()
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                else if (url.lowercase(Locale.US).contains(".png")) {
 | 
			
		||||
                    try {
 | 
			
		||||
                        val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
 | 
			
		||||
                        return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.PNG))
 | 
			
		||||
                    }catch ( e : ExecutionException) {}
 | 
			
		||||
                    } catch ( e : ExecutionException) {
 | 
			
		||||
                        e.sendSilentlyWithAcra()
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                else if (url.lowercase(Locale.US).contains(".webp")) {
 | 
			
		||||
                    try {
 | 
			
		||||
                        val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
 | 
			
		||||
                        return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.WEBP))
 | 
			
		||||
                    }catch ( e : ExecutionException) {}
 | 
			
		||||
                    } catch ( e : ExecutionException) {
 | 
			
		||||
                        e.sendSilentlyWithAcra()
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return super.shouldInterceptRequest(view, url)
 | 
			
		||||
@@ -395,6 +405,7 @@ class ArticleFragment : Fragment(), DIAware {
 | 
			
		||||
            val itemUrl = URL(url)
 | 
			
		||||
            baseUrl = itemUrl.protocol + "://" + itemUrl.host
 | 
			
		||||
        } catch (e: MalformedURLException) {
 | 
			
		||||
            e.sendSilentlyWithAcra()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val fontName =  when (font) {
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@ import bou.amine.apps.readerforselfossv2.utils.getImages
 | 
			
		||||
import com.bumptech.glide.Glide
 | 
			
		||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
 | 
			
		||||
import com.bumptech.glide.request.RequestOptions
 | 
			
		||||
import org.acra.ktx.sendSilentlyWithAcra
 | 
			
		||||
 | 
			
		||||
fun SelfossModel.Item.preloadImages(context: Context) : Boolean {
 | 
			
		||||
    val imageUrls = this.getImages()
 | 
			
		||||
@@ -23,6 +24,7 @@ fun SelfossModel.Item.preloadImages(context: Context) : Boolean {
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    } catch (e : Error) {
 | 
			
		||||
        e.sendSilentlyWithAcra()
 | 
			
		||||
        return false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -35,7 +37,7 @@ fun String.toTextDrawableString(): String {
 | 
			
		||||
        try {
 | 
			
		||||
            textDrawable.append(s[0])
 | 
			
		||||
        } catch (e: StringIndexOutOfBoundsException) {
 | 
			
		||||
            // We do nothing
 | 
			
		||||
            e.sendSilentlyWithAcra()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return textDrawable.toString()
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,8 @@ import androidx.preference.PreferenceFragmentCompat
 | 
			
		||||
import bou.amine.apps.readerforselfossv2.android.R
 | 
			
		||||
import bou.amine.apps.readerforselfossv2.android.databinding.ActivitySettingsBinding
 | 
			
		||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
 | 
			
		||||
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
 | 
			
		||||
@@ -101,6 +103,11 @@ class SettingsActivity : AppCompatActivity(),
 | 
			
		||||
    class MainPreferenceFragment : PreferenceFragmentCompat() {
 | 
			
		||||
        override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
 | 
			
		||||
            setPreferencesFromResource(R.xml.pref_main, rootKey)
 | 
			
		||||
 | 
			
		||||
            preferenceManager.findPreference<Preference>("currentMode")?.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
 | 
			
		||||
                AppCompatDelegate.setDefaultNightMode(newValue.toString().toInt()) // ListPreference Only takes string-arrays ¯\_(ツ)_/¯
 | 
			
		||||
                true
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -117,6 +124,7 @@ class SettingsActivity : AppCompatActivity(),
 | 
			
		||||
                                val input: Int = (dest.toString() + source.toString()).toInt()
 | 
			
		||||
                                if (input in 1..200) return@InputFilter null
 | 
			
		||||
                            } catch (nfe: NumberFormatException) {
 | 
			
		||||
                                nfe.sendSilentlyWithAcra()
 | 
			
		||||
                                Toast.makeText(activity, R.string.items_number_should_be_number, Toast.LENGTH_LONG).show()
 | 
			
		||||
                            }
 | 
			
		||||
                            ""
 | 
			
		||||
@@ -140,6 +148,7 @@ class SettingsActivity : AppCompatActivity(),
 | 
			
		||||
                        try {
 | 
			
		||||
                            editText.textSize = editable.toString().toInt().toFloat()
 | 
			
		||||
                        } catch (e: NumberFormatException) {
 | 
			
		||||
                            e.sendSilentlyWithAcra()
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                } }
 | 
			
		||||
@@ -149,6 +158,7 @@ class SettingsActivity : AppCompatActivity(),
 | 
			
		||||
                                val input = (dest.toString() + source.toString()).toInt()
 | 
			
		||||
                                if (input > 0) return@InputFilter null
 | 
			
		||||
                            } catch (nfe: NumberFormatException) {
 | 
			
		||||
                                nfe.sendSilentlyWithAcra()
 | 
			
		||||
                            }
 | 
			
		||||
                            ""
 | 
			
		||||
                        }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,9 @@
 | 
			
		||||
package bou.amine.apps.readerforselfossv2.android.utils
 | 
			
		||||
 | 
			
		||||
import android.app.Activity
 | 
			
		||||
import android.app.PendingIntent
 | 
			
		||||
import android.content.ActivityNotFoundException
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.content.Intent
 | 
			
		||||
import android.graphics.BitmapFactory
 | 
			
		||||
import android.net.Uri
 | 
			
		||||
import android.os.Build
 | 
			
		||||
import android.text.Spannable
 | 
			
		||||
import android.text.style.ClickableSpan
 | 
			
		||||
import android.util.Patterns
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,5 @@
 | 
			
		||||
<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="M20,8h-2.81c-0.45,-0.78 -1.07,-1.45 -1.82,-1.96L17,4.41 15.59,3l-2.17,2.17C12.96,5.06 12.49,5 12,5c-0.49,0 -0.96,0.06 -1.41,0.17L8.41,3 7,4.41l1.62,1.63C7.88,6.55 7.26,7.22 6.81,8L4,8v2h2.09c-0.05,0.33 -0.09,0.66 -0.09,1v1L4,12v2h2v1c0,0.34 0.04,0.67 0.09,1L4,16v2h2.81c1.04,1.79 2.97,3 5.19,3s4.15,-1.21 5.19,-3L20,18v-2h-2.09c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1L20,10L20,8zM14,16h-4v-2h4v2zM14,12h-4v-2h4v2z"/>
 | 
			
		||||
</vector>
 | 
			
		||||
@@ -0,0 +1,7 @@
 | 
			
		||||
<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>
 | 
			
		||||
@@ -134,4 +134,8 @@
 | 
			
		||||
    <string name="mode_light">Light mode</string>
 | 
			
		||||
    <string name="drawer_error_loading_sources">Error loading sources…</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>
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
@@ -134,4 +134,8 @@
 | 
			
		||||
    <string name="mode_light">Light mode</string>
 | 
			
		||||
    <string name="drawer_error_loading_sources">Error loading sources…</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>
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
@@ -134,4 +134,8 @@
 | 
			
		||||
    <string name="mode_light">Light mode</string>
 | 
			
		||||
    <string name="drawer_error_loading_sources">Error loading sources…</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>
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
@@ -134,4 +134,8 @@
 | 
			
		||||
    <string name="mode_light">Light mode</string>
 | 
			
		||||
    <string name="drawer_error_loading_sources">Error loading sources…</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>
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
@@ -134,4 +134,8 @@
 | 
			
		||||
    <string name="mode_light">Thème clair</string>
 | 
			
		||||
    <string name="drawer_error_loading_sources">Error loading sources…</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>
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
@@ -134,4 +134,8 @@
 | 
			
		||||
    <string name="mode_light">Light mode</string>
 | 
			
		||||
    <string name="drawer_error_loading_sources">Error loading sources…</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>
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
@@ -134,4 +134,8 @@
 | 
			
		||||
    <string name="mode_light">Light mode</string>
 | 
			
		||||
    <string name="drawer_error_loading_sources">Error loading sources…</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>
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
@@ -134,4 +134,8 @@
 | 
			
		||||
    <string name="mode_light">Light mode</string>
 | 
			
		||||
    <string name="drawer_error_loading_sources">Error loading sources…</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>
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
@@ -134,4 +134,8 @@
 | 
			
		||||
    <string name="mode_light">Light mode</string>
 | 
			
		||||
    <string name="drawer_error_loading_sources">Error loading sources…</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>
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,8 @@
 | 
			
		||||
<?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>
 | 
			
		||||
</resources>
 | 
			
		||||
@@ -134,4 +134,8 @@
 | 
			
		||||
    <string name="mode_light">Light mode</string>
 | 
			
		||||
    <string name="drawer_error_loading_sources">Error loading sources…</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>
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
@@ -134,4 +134,8 @@
 | 
			
		||||
    <string name="mode_light">Light mode</string>
 | 
			
		||||
    <string name="drawer_error_loading_sources">Error loading sources…</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>
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
@@ -134,4 +134,8 @@
 | 
			
		||||
    <string name="mode_light">Light mode</string>
 | 
			
		||||
    <string name="drawer_error_loading_sources">Error loading sources…</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>
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
@@ -134,4 +134,8 @@
 | 
			
		||||
    <string name="mode_light">Light mode</string>
 | 
			
		||||
    <string name="drawer_error_loading_sources">Error loading sources…</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>
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
@@ -134,4 +134,8 @@
 | 
			
		||||
    <string name="mode_light">Light mode</string>
 | 
			
		||||
    <string name="drawer_error_loading_sources">Error loading sources…</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>
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
@@ -134,4 +134,8 @@
 | 
			
		||||
    <string name="mode_light">浅色模式</string>
 | 
			
		||||
    <string name="drawer_error_loading_sources">Error loading sources…</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>
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
@@ -134,4 +134,8 @@
 | 
			
		||||
    <string name="mode_light">Light mode</string>
 | 
			
		||||
    <string name="drawer_error_loading_sources">Error loading sources…</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>
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
@@ -137,4 +137,8 @@
 | 
			
		||||
    <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>
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,6 @@
 | 
			
		||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    xmlns:app="http://schemas.android.com/apk/res-auto">
 | 
			
		||||
 | 
			
		||||
    <SwitchPreference
 | 
			
		||||
        android:defaultValue="true"
 | 
			
		||||
        app:iconSpaceReserved="false"
 | 
			
		||||
        android:key="enable_analytics"
 | 
			
		||||
        android:title="@string/pref_switch_enable_analytics" />
 | 
			
		||||
 | 
			
		||||
    <EditTextPreference
 | 
			
		||||
        android:inputType="number"
 | 
			
		||||
        android:key="api_timeout"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
			
		||||
    android:title="@string/title_activity_settings">
 | 
			
		||||
 | 
			
		||||
    <Preference
 | 
			
		||||
@@ -17,9 +18,13 @@
 | 
			
		||||
        android:title="@string/pref_header_offline"
 | 
			
		||||
        android:icon="@drawable/ic_signal_wifi_off_black_24dp" />
 | 
			
		||||
 | 
			
		||||
    <Preference
 | 
			
		||||
        android:fragment="bou.amine.apps.readerforselfossv2.android.settings.SettingsActivity$ThemePreferenceFragment"
 | 
			
		||||
    <ListPreference
 | 
			
		||||
        android:defaultValue="0"
 | 
			
		||||
        android:entries="@array/ModeTitles"
 | 
			
		||||
        android:entryValues="@array/ModeValues"
 | 
			
		||||
        android:key="currentMode"
 | 
			
		||||
        android:title="@string/pref_header_theme"
 | 
			
		||||
        app:useSimpleSummaryProvider="false"
 | 
			
		||||
        android:icon="@drawable/ic_color_lens_black_24dp" />
 | 
			
		||||
 | 
			
		||||
    <Preference
 | 
			
		||||
@@ -32,4 +37,18 @@
 | 
			
		||||
        android:title="@string/pref_header_experimental"
 | 
			
		||||
        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"
 | 
			
		||||
        android:title="@string/pref_switch_disable_acra"
 | 
			
		||||
        android:icon="@drawable/ic_baseline_bug_report_24"/>
 | 
			
		||||
 | 
			
		||||
</PreferenceScreen>
 | 
			
		||||
@@ -1023,7 +1023,7 @@ class RepositoryTest {
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun create_source() {
 | 
			
		||||
        coEvery { api.createSourceForVersion(any(), any(), any(), any(), any(), any()) } returns
 | 
			
		||||
        coEvery { api.createSourceForVersion(any(), any(), any(), any(), any()) } returns
 | 
			
		||||
                SuccessResponse(true)
 | 
			
		||||
 | 
			
		||||
        initializeRepository()
 | 
			
		||||
@@ -1045,7 +1045,6 @@ class RepositoryTest {
 | 
			
		||||
                any(),
 | 
			
		||||
                any(),
 | 
			
		||||
                any(),
 | 
			
		||||
                any()
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
        assertSame(true, response)
 | 
			
		||||
@@ -1053,7 +1052,7 @@ class RepositoryTest {
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun create_source_but_response_fails() {
 | 
			
		||||
        coEvery { api.createSourceForVersion(any(), any(), any(), any(), any(), any()) } returns
 | 
			
		||||
        coEvery { api.createSourceForVersion(any(), any(), any(), any(), any()) } returns
 | 
			
		||||
                SuccessResponse(false)
 | 
			
		||||
 | 
			
		||||
        initializeRepository()
 | 
			
		||||
@@ -1075,7 +1074,6 @@ class RepositoryTest {
 | 
			
		||||
                any(),
 | 
			
		||||
                any(),
 | 
			
		||||
                any(),
 | 
			
		||||
                any()
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
        assertSame(false, response)
 | 
			
		||||
@@ -1083,7 +1081,7 @@ class RepositoryTest {
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun create_source_without_connection() {
 | 
			
		||||
        coEvery { api.createSourceForVersion(any(), any(), any(), any(), any(), any()) } returns
 | 
			
		||||
        coEvery { api.createSourceForVersion(any(), any(), any(), any(), any()) } returns
 | 
			
		||||
                SuccessResponse(true)
 | 
			
		||||
 | 
			
		||||
        initializeRepository(MutableStateFlow(false))
 | 
			
		||||
@@ -1104,7 +1102,6 @@ class RepositoryTest {
 | 
			
		||||
                any(),
 | 
			
		||||
                any(),
 | 
			
		||||
                any(),
 | 
			
		||||
                any(),
 | 
			
		||||
                any()
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,14 @@ class StatusAndData<T>(val success: Boolean, val data: T? = null) {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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()
 | 
			
		||||
 
 | 
			
		||||
@@ -340,8 +340,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
 | 
			
		||||
                url,
 | 
			
		||||
                spout,
 | 
			
		||||
                tags,
 | 
			
		||||
                filter,
 | 
			
		||||
                appSettingsService.getApiVersion()
 | 
			
		||||
                filter
 | 
			
		||||
            ).isSuccess == true
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -373,12 +372,29 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
 | 
			
		||||
                val response = api.login()
 | 
			
		||||
                result = response.isSuccess == true
 | 
			
		||||
            } catch (cause: Throwable) {
 | 
			
		||||
                Napier.e(cause.stackTraceToString(), tag = "RepositoryImpl.updateRemote")
 | 
			
		||||
                Napier.e(cause.stackTraceToString(), tag = "RepositoryImpl.login")
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return result
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    suspend fun logout() {
 | 
			
		||||
        if (isNetworkAvailable()) {
 | 
			
		||||
            try {
 | 
			
		||||
                val response = api.logout()
 | 
			
		||||
                if (response.isSuccess) {
 | 
			
		||||
                    Napier.e("Couldn't logout.", tag = "RepositoryImpl.logout")
 | 
			
		||||
                }
 | 
			
		||||
            } catch (cause: Throwable) {
 | 
			
		||||
                Napier.e(cause.stackTraceToString(), tag = "RepositoryImpl.logout")
 | 
			
		||||
            } finally {
 | 
			
		||||
                appSettingsService.clearAll()
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            appSettingsService.clearAll()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun refreshLoginInformation(url: String, login: String, password: String) {
 | 
			
		||||
        appSettingsService.refreshLoginInformation(url, login, password)
 | 
			
		||||
        baseUrl = url
 | 
			
		||||
 
 | 
			
		||||
@@ -2,11 +2,12 @@ package bou.amine.apps.readerforselfossv2.rest
 | 
			
		||||
 | 
			
		||||
import bou.amine.apps.readerforselfossv2.model.*
 | 
			
		||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
 | 
			
		||||
import io.github.aakira.napier.Napier
 | 
			
		||||
import io.ktor.client.*
 | 
			
		||||
import io.ktor.client.call.*
 | 
			
		||||
import io.ktor.client.plugins.*
 | 
			
		||||
import io.ktor.client.plugins.cache.*
 | 
			
		||||
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.*
 | 
			
		||||
@@ -20,7 +21,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
    var client = createHttpClient()
 | 
			
		||||
 | 
			
		||||
    private fun createHttpClient(): HttpClient {
 | 
			
		||||
        return HttpClient {
 | 
			
		||||
        val client = HttpClient {
 | 
			
		||||
            install(ContentNegotiation) {
 | 
			
		||||
                install(HttpCache)
 | 
			
		||||
                json(Json {
 | 
			
		||||
@@ -32,7 +33,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
            install(Logging) {
 | 
			
		||||
                logger = object : Logger {
 | 
			
		||||
                    override fun log(message: String) {
 | 
			
		||||
                        appSettingsService.logApiCalls(message)
 | 
			
		||||
                        Napier.d(message, tag = "LogApiCalls")
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                level = LogLevel.INFO
 | 
			
		||||
@@ -40,22 +41,26 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
            install(HttpTimeout) {
 | 
			
		||||
                requestTimeoutMillis = appSettingsService.getApiTimeout()
 | 
			
		||||
            }
 | 
			
		||||
            /* TODO: Auth as basic
 | 
			
		||||
            if (apiDetailsService.getUserName().isNotEmpty() && apiDetailsService.getPassword().isNotEmpty()) {
 | 
			
		||||
 | 
			
		||||
                install(Auth) {
 | 
			
		||||
                    basic {
 | 
			
		||||
                        credentials {
 | 
			
		||||
                            BasicAuthCredentials(username = apiDetailsService.getUserName(), password = apiDetailsService.getPassword())
 | 
			
		||||
                        }
 | 
			
		||||
                        sendWithoutRequest {
 | 
			
		||||
                            true
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }*/
 | 
			
		||||
            install(HttpCookies)
 | 
			
		||||
            expectSuccess = false
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        client.plugin(HttpSend).intercept { request ->
 | 
			
		||||
            val originalCall = execute(request)
 | 
			
		||||
            if (originalCall.response.status == HttpStatusCode.Forbidden && shouldHavePostLogin() && hasLoginInfo()) {
 | 
			
		||||
                Napier.i("Forbidden action, will try to login and retry", tag = "HttpSend")
 | 
			
		||||
 | 
			
		||||
                if (login().isSuccess) {
 | 
			
		||||
                    Napier.i("Logged in worked", tag = "HttpSend")
 | 
			
		||||
                    execute(request)
 | 
			
		||||
                }
 | 
			
		||||
                originalCall
 | 
			
		||||
            } else {
 | 
			
		||||
                originalCall
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return client
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun url(path: String) =
 | 
			
		||||
@@ -66,11 +71,38 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
        client = createHttpClient()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 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
 | 
			
		||||
 | 
			
		||||
    suspend fun login(): SuccessResponse =
 | 
			
		||||
        maybeResponse(client.get(url("/login")) {
 | 
			
		||||
            parameter("username", appSettingsService.getUserName())
 | 
			
		||||
            parameter("password", appSettingsService.getPassword())
 | 
			
		||||
        })
 | 
			
		||||
        if (shouldHavePostLogin()) {
 | 
			
		||||
            postLogin()
 | 
			
		||||
        } else {
 | 
			
		||||
            getLogin()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    private suspend fun getLogin() = maybeResponse(client.get(url("/login")) {
 | 
			
		||||
        parameter("username", appSettingsService.getUserName())
 | 
			
		||||
        parameter("password", appSettingsService.getPassword())
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    private suspend fun postLogin() = maybeResponse(client.post(url("/login")) {
 | 
			
		||||
        parameter("username", appSettingsService.getUserName())
 | 
			
		||||
        parameter("password", appSettingsService.getPassword())
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    private fun shouldHaveNewLogout() = appSettingsService.getApiVersion() >= 5 // We are missing 4.1.0
 | 
			
		||||
    suspend fun logout(): SuccessResponse =
 | 
			
		||||
        if (shouldHaveNewLogout()) {
 | 
			
		||||
            doLogout()
 | 
			
		||||
        } else {
 | 
			
		||||
            maybeLogoutIfAvailable()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    private suspend fun maybeLogoutIfAvailable() = responseOrSuccessIf404(client.get(url("/logout")))
 | 
			
		||||
 | 
			
		||||
    private suspend fun doLogout() = maybeResponse(client.delete(url("/api/session/current")))
 | 
			
		||||
 | 
			
		||||
    suspend fun getItems(
 | 
			
		||||
        type: String,
 | 
			
		||||
@@ -82,80 +114,102 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
        items: Int? = null
 | 
			
		||||
    ): StatusAndData<List<SelfossModel.Item>> =
 | 
			
		||||
        bodyOrFailure(client.get(url("/items")) {
 | 
			
		||||
            if (!shouldHavePostLogin()) {
 | 
			
		||||
                parameter("username", appSettingsService.getUserName())
 | 
			
		||||
                parameter("password", appSettingsService.getPassword())
 | 
			
		||||
                parameter("type", type)
 | 
			
		||||
                parameter("tag", tag)
 | 
			
		||||
                parameter("source", source)
 | 
			
		||||
                parameter("search", search)
 | 
			
		||||
                parameter("updatedsince", updatedSince)
 | 
			
		||||
                parameter("items", items ?: appSettingsService.getItemsNumber())
 | 
			
		||||
                parameter("offset", offset)
 | 
			
		||||
            })
 | 
			
		||||
            }
 | 
			
		||||
            parameter("type", type)
 | 
			
		||||
            parameter("tag", tag)
 | 
			
		||||
            parameter("source", source)
 | 
			
		||||
            parameter("search", search)
 | 
			
		||||
            parameter("updatedsince", updatedSince)
 | 
			
		||||
            parameter("items", items ?: appSettingsService.getItemsNumber())
 | 
			
		||||
            parameter("offset", offset)
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    suspend fun stats(): StatusAndData<SelfossModel.Stats> =
 | 
			
		||||
        bodyOrFailure(client.get(url("/stats")) {
 | 
			
		||||
            if (!shouldHavePostLogin()) {
 | 
			
		||||
                parameter("username", appSettingsService.getUserName())
 | 
			
		||||
                parameter("password", appSettingsService.getPassword())
 | 
			
		||||
            })
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    suspend fun tags(): StatusAndData<List<SelfossModel.Tag>> =
 | 
			
		||||
        bodyOrFailure(client.get(url("/tags")) {
 | 
			
		||||
            if (!shouldHavePostLogin()) {
 | 
			
		||||
                parameter("username", appSettingsService.getUserName())
 | 
			
		||||
                parameter("password", appSettingsService.getPassword())
 | 
			
		||||
            })
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    suspend fun update(): StatusAndData<String> =
 | 
			
		||||
        bodyOrFailure(client.get(url("/update")) {
 | 
			
		||||
            if (!shouldHavePostLogin()) {
 | 
			
		||||
                parameter("username", appSettingsService.getUserName())
 | 
			
		||||
                parameter("password", appSettingsService.getPassword())
 | 
			
		||||
            })
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    suspend fun spouts(): StatusAndData<Map<String, SelfossModel.Spout>> =
 | 
			
		||||
        bodyOrFailure(client.get(url("/sources/spouts")) {
 | 
			
		||||
            if (!shouldHavePostLogin()) {
 | 
			
		||||
                parameter("username", appSettingsService.getUserName())
 | 
			
		||||
                parameter("password", appSettingsService.getPassword())
 | 
			
		||||
            })
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    suspend fun sources(): StatusAndData<ArrayList<SelfossModel.Source>> =
 | 
			
		||||
        bodyOrFailure(client.get(url("/sources/list")) {
 | 
			
		||||
            if (!shouldHavePostLogin()) {
 | 
			
		||||
                parameter("username", appSettingsService.getUserName())
 | 
			
		||||
                parameter("password", appSettingsService.getPassword())
 | 
			
		||||
            })
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    suspend fun version(): StatusAndData<SelfossModel.ApiVersion> =
 | 
			
		||||
        bodyOrFailure(client.get(url("/api/about")))
 | 
			
		||||
 | 
			
		||||
    suspend fun markAsRead(id: String): SuccessResponse =
 | 
			
		||||
        maybeResponse(client.post(url("/mark/$id")) {
 | 
			
		||||
            parameter("username", appSettingsService.getUserName())
 | 
			
		||||
            parameter("password", appSettingsService.getPassword())
 | 
			
		||||
            if (!shouldHavePostLogin()) {
 | 
			
		||||
                parameter("username", appSettingsService.getUserName())
 | 
			
		||||
                parameter("password", appSettingsService.getPassword())
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    suspend fun unmarkAsRead(id: String): SuccessResponse =
 | 
			
		||||
        maybeResponse(client.post(url("/unmark/$id")) {
 | 
			
		||||
            parameter("username", appSettingsService.getUserName())
 | 
			
		||||
            parameter("password", appSettingsService.getPassword())
 | 
			
		||||
            if (!shouldHavePostLogin()) {
 | 
			
		||||
                parameter("username", appSettingsService.getUserName())
 | 
			
		||||
                parameter("password", appSettingsService.getPassword())
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    suspend fun starr(id: String): SuccessResponse =
 | 
			
		||||
        maybeResponse(client.post(url("/starr/$id")) {
 | 
			
		||||
            parameter("username", appSettingsService.getUserName())
 | 
			
		||||
            parameter("password", appSettingsService.getPassword())
 | 
			
		||||
            if (!shouldHavePostLogin()) {
 | 
			
		||||
                parameter("username", appSettingsService.getUserName())
 | 
			
		||||
                parameter("password", appSettingsService.getPassword())
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    suspend fun unstarr(id: String): SuccessResponse =
 | 
			
		||||
        maybeResponse(client.post(url("/unstarr/$id")) {
 | 
			
		||||
            parameter("username", appSettingsService.getUserName())
 | 
			
		||||
            parameter("password", appSettingsService.getPassword())
 | 
			
		||||
            if (!shouldHavePostLogin()) {
 | 
			
		||||
                parameter("username", appSettingsService.getUserName())
 | 
			
		||||
                parameter("password", appSettingsService.getPassword())
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    suspend fun markAllAsRead(ids: List<String>): SuccessResponse =
 | 
			
		||||
        maybeResponse(client.submitForm(
 | 
			
		||||
            url = url("/mark"),
 | 
			
		||||
            formParameters = Parameters.build {
 | 
			
		||||
                append("username", appSettingsService.getUserName())
 | 
			
		||||
                append("password", appSettingsService.getPassword())
 | 
			
		||||
                if (!shouldHavePostLogin()) {
 | 
			
		||||
                    append("username", appSettingsService.getUserName())
 | 
			
		||||
                    append("password", appSettingsService.getPassword())
 | 
			
		||||
                }
 | 
			
		||||
                ids.map { append("ids[]", it) }
 | 
			
		||||
            }
 | 
			
		||||
        ))
 | 
			
		||||
@@ -165,18 +219,17 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
        url: String,
 | 
			
		||||
        spout: String,
 | 
			
		||||
        tags: String,
 | 
			
		||||
        filter: String,
 | 
			
		||||
        version: Int
 | 
			
		||||
        filter: String
 | 
			
		||||
    ): SuccessResponse =
 | 
			
		||||
        maybeResponse(
 | 
			
		||||
            if (version > 1) {
 | 
			
		||||
            if (appSettingsService.getApiVersion() > 1) {
 | 
			
		||||
                createSource2(title, url, spout, tags, filter)
 | 
			
		||||
            } else {
 | 
			
		||||
                createSource(title, url, spout, tags, filter)
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    suspend fun createSource(
 | 
			
		||||
    private suspend fun createSource(
 | 
			
		||||
        title: String,
 | 
			
		||||
        url: String,
 | 
			
		||||
        spout: String,
 | 
			
		||||
@@ -184,8 +237,13 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
        filter: String
 | 
			
		||||
    ): HttpResponse =
 | 
			
		||||
        client.submitForm(
 | 
			
		||||
            url = url("/source?username=${appSettingsService.getUserName()}&password=${appSettingsService.getPassword()}"),
 | 
			
		||||
            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)
 | 
			
		||||
@@ -194,7 +252,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    suspend fun createSource2(
 | 
			
		||||
    private suspend fun createSource2(
 | 
			
		||||
        title: String,
 | 
			
		||||
        url: String,
 | 
			
		||||
        spout: String,
 | 
			
		||||
@@ -202,8 +260,12 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
        filter: String
 | 
			
		||||
    ): HttpResponse =
 | 
			
		||||
        client.submitForm(
 | 
			
		||||
            url = url("/source?username=${appSettingsService.getUserName()}&password=${appSettingsService.getPassword()}"),
 | 
			
		||||
            url = url("/source"),
 | 
			
		||||
            formParameters = Parameters.build {
 | 
			
		||||
                if (!shouldHavePostLogin()) {
 | 
			
		||||
                    append("username", appSettingsService.getUserName())
 | 
			
		||||
                    append("password", appSettingsService.getPassword())
 | 
			
		||||
                }
 | 
			
		||||
                append("title", title)
 | 
			
		||||
                append("url", url)
 | 
			
		||||
                append("spout", spout)
 | 
			
		||||
@@ -214,7 +276,9 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
 | 
			
		||||
 | 
			
		||||
    suspend fun deleteSource(id: Int): SuccessResponse =
 | 
			
		||||
        maybeResponse(client.delete(url("/source/$id")) {
 | 
			
		||||
            if (!shouldHavePostLogin()) {
 | 
			
		||||
                parameter("username", appSettingsService.getUserName())
 | 
			
		||||
                parameter("password", appSettingsService.getPassword())
 | 
			
		||||
            })
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
}
 | 
			
		||||
@@ -44,10 +44,6 @@ class AppSettingsService {
 | 
			
		||||
        refreshUserSettings()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun logApiCalls(message: String) {
 | 
			
		||||
        Napier.d(message, tag = "LogApiCalls")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun getApiVersion(): Int {
 | 
			
		||||
        if (_apiVersion == -1) {
 | 
			
		||||
            refreshApiVersion()
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user