Compare commits

...

25 Commits

Author SHA1 Message Date
0aac2cba0b Android gradle plugin
All checks were successful
continuous-integration/drone/pr Build is passing
2023-09-07 11:55:15 +02:00
42e1c1ebbd Downgrade gradle version
Some checks failed
continuous-integration/drone/pr Build is failing
2023-08-22 16:49:48 +02:00
a67a2a0927 Use forEachIndexed to support older apis
Some checks failed
continuous-integration/drone/pr Build is failing
2023-08-21 23:25:35 +02:00
24beee070c Remove unused indexed function call
Some checks failed
continuous-integration/drone/pr Build is failing
2023-08-19 11:56:54 +02:00
1e6bc57751 Revert to Java version 11 2023-08-19 11:56:54 +02:00
85d966f54b Fix naming and reintroduce the xmlns
Some checks failed
continuous-integration/drone/pr Build is failing
2023-08-10 21:56:07 +02:00
3fff8eeeb6 Handle most HTTP client creation in common code 2023-08-10 21:43:13 +02:00
5eb5a5658f Add translations
All checks were successful
continuous-integration/drone/pr Build is passing
2023-07-15 13:48:23 +02:00
3646658cb1 Fix R8 compilation problem
Some checks failed
continuous-integration/drone/pr Build is failing
2023-07-15 13:25:51 +02:00
b793301706 Add a login switch to disable SSL verification
Some checks failed
continuous-integration/drone/pr Build is failing
2023-07-13 14:55:48 +02:00
313042955c Upgrade to gradle 8 2023-07-13 14:55:48 +02:00
c4f4bafe85 Add a switch in the login screen to disable SSL 2023-07-13 14:55:48 +02:00
ed06b22a77 Tentative self signed ssl support 2023-07-13 14:55:48 +02:00
aminecmi
172362b533 Changelog for v123061811 [CI SKIP] 2023-06-30 19:06:44 +00:00
aminecmi
ad72cb6f56 feat: Added confirmation dialog for disconnect item menu.
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
2023-06-30 20:37:43 +02:00
aminecmi
9057ee0052 Changelog for v123061651 [CI SKIP] 2023-06-14 18:23:59 +00:00
aminecmi
50d0b44315 i18n: Translation update.
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
2023-06-13 20:52:42 +02:00
aminecmi
21b08ed384 i18n: Translation update.
Some checks are pending
continuous-integration/drone/push Build is running
2023-06-13 20:26:36 +02:00
aminecmi
993c4d2ee9 i18n: Translation update.
All checks were successful
continuous-integration/drone/push Build is passing
2023-06-12 20:50:26 +02:00
aminecmi
57a9d51027 fix: avoid trying to open invalid image urls.
Some checks are pending
continuous-integration/drone/push Build is running
2023-06-12 20:34:35 +02:00
aminecmi
673f0edb8b Changelog for v123051471 [CI SKIP] 2023-05-27 19:25:35 +00:00
aminecmi
7f96798f13 fix: images could be null.
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
2023-05-27 21:06:56 +02:00
aminecmi
6e5704a45b fix: Check if color is not empty before parsing it. 2023-05-27 21:02:25 +02:00
aminecmi
495591159f chore: Removed unused log. 2023-05-27 21:01:54 +02:00
aminecmi
718fe7c5ee Changelog for v123051331 [CI SKIP] 2023-05-13 20:24:57 +00:00
34 changed files with 287 additions and 141 deletions

View File

@@ -1,3 +1,36 @@
**v123061811**
- feat: Added confirmation dialog for disconnect item menu.
- Changelog for v123061651 [CI SKIP]
--------------------------------------------------------------------
**v123061651**
- i18n: Translation update.
- i18n: Translation update.
- i18n: Translation update.
- fix: avoid trying to open invalid image urls.
- Changelog for v123051471 [CI SKIP]
--------------------------------------------------------------------
**v123051471**
- fix: images could be null.
- fix: Check if color is not empty before parsing it.
- chore: Removed unused log.
- Changelog for v123051331 [CI SKIP]
--------------------------------------------------------------------
**v123051331**
- fix: illegal input.
- Changelog for v123051321 [CI SKIP]
--------------------------------------------------------------------
**v123051321**
- debug: Debug null context.

View File

@@ -8,7 +8,7 @@ plugins {
kotlin("android")
kotlin("kapt")
id("com.mikepenz.aboutlibraries.plugin")
id("org.jetbrains.kotlinx.kover") version "0.6.1"
id("org.jetbrains.kotlinx.kover")
}
fun Project.execWithOutput(cmd: String, ignore: Boolean = false): String {
@@ -16,7 +16,7 @@ fun Project.execWithOutput(cmd: String, ignore: Boolean = false): String {
project.exec {
commandLine = cmd.split(" ")
standardOutput = outputStream
isIgnoreExitValue = ignore ?: false
isIgnoreExitValue = ignore
}
outputStream.toString()
}
@@ -24,9 +24,8 @@ fun Project.execWithOutput(cmd: String, ignore: Boolean = false): String {
}
fun gitVersion(): String {
var process = ""
val maybeTagOfCurrentCommit = execWithOutput("git -C ../ describe --contains HEAD", true)
process = if (maybeTagOfCurrentCommit.isEmpty()) {
var process = if (maybeTagOfCurrentCommit.isEmpty()) {
println("No tag on current commit. Will take the latest one.")
execWithOutput("git -C ../ for-each-ref refs/tags --sort=-refname --format='%(refname:short)' --count=1")
} else {
@@ -171,7 +170,7 @@ dependencies {
//PhotoView
implementation("com.github.chrisbanes:PhotoView:2.3.0")
implementation("androidx.core:core-ktx:1.8.0")
implementation("androidx.core:core-ktx:1.10.1")
implementation("androidx.lifecycle:lifecycle-extensions:2.2.0")

View File

@@ -55,6 +55,7 @@
# maybe remove later ?
-keep class * extends androidx.fragment.app.Fragment
-dontwarn org.slf4j.impl.StaticLoggerBinder
# Keep `Companion` object fields of serializable classes.
# This avoids serializer lookup through `getDeclaredClasses` as done for named companion objects.

View File

@@ -599,12 +599,14 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
return true
}
R.id.action_disconnect -> {
runBlocking {
repository.logout()
needsConfirmation(R.string.confirm_disconnect_title, R.string.confirm_disconnect_description) {
runBlocking {
repository.logout()
}
val intent = Intent(this, LoginActivity::class.java)
this.startActivity(intent)
finish()
}
val intent = Intent(this, LoginActivity::class.java)
this.startActivity(intent)
finish()
return true
}
R.id.action_settings -> {

View File

@@ -139,9 +139,12 @@ class LoginActivity : AppCompatActivity(), DIAware {
showProgress(true)
appSettingsService.updateSelfSigned(binding.selfSigned.isChecked)
repository.refreshLoginInformation(url, login, password)
CoroutineScope(Dispatchers.Main).launch {
repository.updateApiInformation()
val result = repository.login()
if (result) {
val (errorFetching, displaySelfossOnly) = repository.shouldBeSelfossInstance()

View File

@@ -74,7 +74,6 @@ class ReaderActivity : AppCompatActivity(), DIAware {
try {
readItem(allItems[currentItem])
} catch (e: IndexOutOfBoundsException) {
e.sendSilentlyWithAcraWithName("out of bound > size = ${allItems.size} currentItem = $currentItem")
finish()
}

View File

@@ -27,6 +27,7 @@ import bou.amine.apps.readerforselfossv2.android.model.toModel
import bou.amine.apps.readerforselfossv2.android.model.toParcelable
import bou.amine.apps.readerforselfossv2.android.sendSilentlyWithAcraWithName
import bou.amine.apps.readerforselfossv2.android.utils.glide.getBitmapInputStream
import bou.amine.apps.readerforselfossv2.android.utils.isUrlValid
import bou.amine.apps.readerforselfossv2.android.utils.openInBrowserAsNewTask
import bou.amine.apps.readerforselfossv2.android.utils.shareLink
import bou.amine.apps.readerforselfossv2.model.MercuryModel
@@ -314,7 +315,7 @@ class ArticleFragment : Fragment(), DIAware {
binding.webcontent.webViewClient = object : WebViewClient() {
@Deprecated("Deprecated in Java")
override fun shouldOverrideUrlLoading(view: WebView?, url: String): Boolean {
return if (context != null && binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
return if (context != null && url.isUrlValid() && binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
try {
requireContext().startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
} catch (e: ActivityNotFoundException) {
@@ -553,8 +554,8 @@ class ArticleFragment : Fragment(), DIAware {
}
fun performClick(): Boolean {
if (binding.webcontent.hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE ||
binding.webcontent.hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE
if (allImages != null && (binding.webcontent.hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE ||
binding.webcontent.hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE)
) {
val position: Int = allImages.indexOf(binding.webcontent.hitTestResult.extra)

View File

@@ -82,7 +82,7 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
) {
val sourceGroup = binding.sourcesGroup
repository.getSourcesDetailsOrStats().forEach { source ->
repository.getSourcesDetailsOrStats().forEachIndexed { _, source ->
val c = Chip(context)
c.ellipsize = TextUtils.TruncateAt.END
@@ -144,26 +144,28 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
val tags = repository.getTags()
tags.forEach { tag ->
tags.forEachIndexed { _, tag ->
val c = Chip(context)
c.ellipsize = TextUtils.TruncateAt.END
c.text = tag.tag
try {
val gd = GradientDrawable()
val gdColor = try {
Color.parseColor(tag.color)
} catch (e: IllegalArgumentException) {
e.sendSilentlyWithAcraWithName("color issue " + tag.color)
resources.getColor(R.color.colorPrimary)
if (tag.color.isNotEmpty()) {
try {
val gd = GradientDrawable()
val gdColor = try {
Color.parseColor(tag.color)
} catch (e: IllegalArgumentException) {
e.sendSilentlyWithAcraWithName("color issue " + tag.color)
resources.getColor(R.color.colorPrimary)
}
gd.setColor(gdColor)
gd.shape = GradientDrawable.RECTANGLE
gd.setSize(30, 30)
gd.cornerRadius = 30F
c.chipIcon = gd
} catch (e: Exception) {
e.sendSilentlyWithAcraWithName("tags > GradientDrawable")
}
gd.setColor(gdColor)
gd.shape = GradientDrawable.RECTANGLE
gd.setSize(30, 30)
gd.cornerRadius = 30F
c.chipIcon = gd
} catch (e: Exception) {
e.sendSilentlyWithAcraWithName("tags > GradientDrawable")
}
c.setOnCloseIconClickListener {

View File

@@ -51,6 +51,13 @@
android:maxLines="1"
android:minHeight="48dp" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/selfSigned"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/disable_ssl"
android:textAlignment="viewStart" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/withLogin"
android:layout_width="match_parent"

View File

@@ -128,4 +128,7 @@
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
<string name="confirm_disconnect_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources>

View File

@@ -128,4 +128,7 @@
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
<string name="confirm_disconnect_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources>

View File

@@ -128,4 +128,7 @@
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
<string name="confirm_disconnect_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources>

View File

@@ -128,4 +128,7 @@
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
<string name="confirm_disconnect_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources>

View File

@@ -128,4 +128,7 @@
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
<string name="confirm_disconnect_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources>

View File

@@ -33,8 +33,8 @@
<string name="addStringNoUrl">"Accede pra engadir fontes."</string>
<string name="cant_get_sources">"Non se pode obter a lista de fontes."</string>
<string name="cant_create_source">"Non se pode crear unha fonte."</string>
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
<string name="cant_get_spouts">"Can't get spouts list. There may ben an api issue."</string>
<string name="cant_get_spouts_no_network">"Non se pode obter a lista de spouts por mor dun erro de rede."</string>
<string name="cant_get_spouts">"Non se pode obter a lista de spoits. Pode que haxa algún problema coa api."</string>
<string name="form_not_complete">"O formulario non está completo"</string>
<string name="pref_header_links">"Ligazóns"</string>
<string name="issue_tracker_link">"Rastrexador de Incidencias"</string>
@@ -116,16 +116,19 @@
<string name="reader_static_bar_on">A barra inferior mostrarase sempre</string>
<string name="reader_static_bar_off">A barra inferior pode mostrarse a través do botón flotante</string>
<string name="remove_source">Eliminar fonte</string>
<string name="pref_theme_title">Light/Dark mode</string>
<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="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>
<string name="pref_theme_title">Modo Claro/Escuro</string>
<string name="mode_dark">Modo escuro</string>
<string name="mode_system">Seguir axustes do sistema</string>
<string name="mode_light">Modo claro</string>
<string name="gdpr_dialog_title">A aplicación non comparte ningún dato persoal seu.</string>
<string name="gdpr_dialog_message"><![CDATA[O envío de informes de erros está habilitado. Pode deshabilitarse dende a páxina de axustes. Ten en conta que os informes de erros son esenciais para o desenvolvemento da aplicación.]]></string>
<string name="crash_toast_text">Ocurriu un erro. Enviando os detalles o desenvolvedor.</string>
<string name="pref_switch_disable_acra">"Deshabilitar o reporte automático de erros. "</string>
<string name="menu_home_filter">Filtros</string>
<string name="application_selfoss_only">Esta aplicación só funciona cunha instancia de Selfoss, e con ningún outro filtro RSS.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
<string name="confirm_disconnect_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources>

View File

@@ -90,7 +90,7 @@
<string name="pref_switch_items_caching">Save items for offline use</string>
<string name="pref_switch_update_sources">Check for new sources and tags</string>
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
<string name="network_connectivity_lost">"Network connection lost"</string>
<string name="network_connectivity_lost">"Koneksi jaringan hilang"</string>
<string name="network_connectivity_retrieved">"Network connection is now available"</string>
<string name="pref_switch_periodic_refresh">Sync articles</string>
<string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string>
@@ -128,4 +128,7 @@
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
<string name="confirm_disconnect_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<resources>
<string name="app_name">"Lettore RSS per Selfoss"</string>
<string name="title_activity_login">"Accedi"</string>
<string name="prompt_password">"Password"</string>
@@ -128,4 +128,7 @@
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
<string name="confirm_disconnect_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources>

View File

@@ -128,4 +128,7 @@
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
<string name="confirm_disconnect_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<resources xmlns:tools="http://schemas.android.com/tools">
<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>

View File

@@ -128,4 +128,7 @@
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
<string name="confirm_disconnect_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources>

View File

@@ -128,4 +128,7 @@
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
<string name="confirm_disconnect_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources>

View File

@@ -128,4 +128,7 @@
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
<string name="confirm_disconnect_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources>

View File

@@ -128,4 +128,7 @@
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
<string name="confirm_disconnect_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources>

View File

@@ -128,4 +128,7 @@
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
<string name="confirm_disconnect_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources>

View File

@@ -125,7 +125,10 @@
<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>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
<string name="application_selfoss_only">此应用只适用于 Selfoss 实例,不适用于其他 RSS 。</string>
<string name="menu_home_sources"></string>
<string name="update_source">更新源</string>
<string name="confirm_disconnect_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources>

View File

@@ -128,4 +128,7 @@
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
<string name="confirm_disconnect_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources>

View File

@@ -6,6 +6,7 @@
<string name="error_invalid_password">"Password not long enough"</string>
<string name="error_field_required">"Field required"</string>
<string name="prompt_url">"Url"</string>
<string name="disable_ssl">"Disable SSL"</string>
<string name="withLoginSwitch">"Login required ?"</string>
<string name="login_url_problem">"Oops. You may need to add a \"/\" at the end of the url."</string>
<string name="prompt_login">"Username"</string>
@@ -131,4 +132,6 @@
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
<string name="confirm_disconnect_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
</resources>

View File

@@ -7,12 +7,12 @@ buildscript {
plugins {
//trick: for the same plugin versions in all sub-modules
id("com.android.application").version("7.4.0").apply(false)
id("com.android.library").version("7.4.0").apply(false)
kotlin("android").version("1.7.20").apply(false)
kotlin("multiplatform").version("1.7.20").apply(false)
id("com.android.application").version("8.1.0").apply(false)
id("com.android.library").version("8.1.0").apply(false)
id("org.jetbrains.kotlin.android").version("1.9.10").apply(false)
kotlin("multiplatform").version("1.9.10").apply(false)
id("com.mikepenz.aboutlibraries.plugin").version("10.5.1").apply(false)
id("org.jetbrains.kotlinx.kover") version "0.6.1"
id("org.jetbrains.kotlinx.kover").version("0.6.1").apply(true)
}
allprojects {

View File

@@ -13,22 +13,15 @@
#Tue Mar 22 16:50:00 CET 2022
#Gradle
org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M"
#Kotlin
kotlin.code.style=official
#Android
android.useAndroidX=true
kotlin.native.enableDependencyPropagation=false
#android.nonTransitiveRClass=true
android.enableJetifier=true
android.nonTransitiveRClass=false
#MPP
kotlin.mpp.enableCInteropCommonization=true
kotlin.mpp.enableGranularSourceSetsMetadata=true
org.gradle.parallel=true
org.gradle.caching=true
ignoreGitVersion=false

View File

@@ -1,6 +1,6 @@
#Mon Jan 23 20:47:46 CET 2023
#Thu Jul 13 11:41:19 CEST 2023
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -1,3 +1,5 @@
val ktorVersion = "2.3.2"
object SqlDelight {
const val runtime = "com.squareup.sqldelight:runtime:1.5.4"
const val android = "com.squareup.sqldelight:android-driver:1.5.4"
@@ -9,12 +11,13 @@ plugins {
kotlin("multiplatform")
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("plugin.serialization") version "1.9.0"
id("org.jetbrains.kotlinx.kover")
}
kotlin {
android()
androidTarget()
jvmToolchain(11)
listOf(
iosX64(),
@@ -29,13 +32,15 @@ kotlin {
sourceSets {
val commonMain by getting {
dependencies {
implementation("io.ktor:ktor-client-core:2.1.1")
implementation("io.ktor:ktor-client-content-negotiation:2.1.1")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.1.1")
implementation("io.ktor:ktor-client-logging:2.1.1")
implementation("io.ktor:ktor-client-core:$ktorVersion")
implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
implementation("io.ktor:ktor-client-logging:$ktorVersion")
implementation("io.ktor:ktor-client-auth:$ktorVersion")
implementation("io.ktor:ktor-client-cio:$ktorVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")
implementation("io.ktor:ktor-client-auth:2.1.1")
implementation("org.jsoup:jsoup:1.14.3")
implementation("org.jsoup:jsoup:1.15.4")
//Dependency Injection
implementation("org.kodein.di:kodein-di:7.12.0")
@@ -58,14 +63,14 @@ kotlin {
}
val androidMain by getting {
dependencies {
implementation("io.ktor:ktor-client-okhttp:2.1.1")
implementation("com.squareup.okhttp3:okhttp:4.11.0")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
// Sql
implementation(SqlDelight.android)
}
}
val androidTest by getting {
val androidUnitTest by getting {
dependencies {
implementation(kotlin("test-junit"))
implementation("junit:junit:4.13.2")
@@ -98,15 +103,14 @@ kotlin {
}
android {
compileSdk = 32
compileSdk = 33
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig {
minSdk = 21
targetSdk = 32
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
namespace = "bou.amine.apps.readerforselfossv2"
}

View File

@@ -0,0 +1,17 @@
package bou.amine.apps.readerforselfossv2.rest
import io.ktor.client.engine.cio.CIOEngineConfig
import java.security.cert.X509Certificate
import javax.net.ssl.X509TrustManager
class NaiveTrustManager : X509TrustManager {
override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) {}
override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {}
override fun getAcceptedIssuers(): Array<out X509Certificate> = arrayOf()
}
actual fun setupInsecureHTTPEngine(config: CIOEngineConfig) {
config.https.trustManager = NaiveTrustManager()
}

View File

@@ -5,79 +5,93 @@ 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.*
import io.ktor.client.plugins.*
import io.ktor.client.plugins.auth.providers.*
import io.ktor.client.plugins.cache.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.plugins.cookies.*
import io.ktor.client.plugins.logging.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import io.ktor.util.*
import io.ktor.utils.io.charsets.*
import io.ktor.utils.io.core.*
import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO
import io.ktor.client.engine.cio.CIOEngineConfig
import io.ktor.client.plugins.HttpRequestRetry
import io.ktor.client.plugins.HttpTimeout
import io.ktor.client.plugins.auth.providers.BasicAuthCredentials
import io.ktor.client.plugins.cache.HttpCache
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.cookies.HttpCookies
import io.ktor.client.plugins.logging.LogLevel
import io.ktor.client.plugins.logging.Logger
import io.ktor.client.plugins.logging.Logging
import io.ktor.client.request.get
import io.ktor.client.request.headers
import io.ktor.client.request.parameter
import io.ktor.client.statement.HttpResponse
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpStatusCode
import io.ktor.http.Parameters
import io.ktor.serialization.kotlinx.json.json
import io.ktor.util.encodeBase64
import io.ktor.utils.io.charsets.Charsets
import io.ktor.utils.io.core.toByteArray
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json
expect fun setupInsecureHTTPEngine(config: CIOEngineConfig)
fun createHttpClient(
appSettingsService: AppSettingsService,
api: SelfossApi
) =
HttpClient(CIO) {
if (appSettingsService.getSelfSigned()) {
engine {
setupInsecureHTTPEngine(this)
}
}
install(ContentNegotiation) {
install(HttpCache)
json(Json {
prettyPrint = true
isLenient = true
ignoreUnknownKeys = true
})
}
install(Logging) {
logger = object : Logger {
override fun log(message: String) {
Napier.d(message, tag = "LogApiCalls")
}
}
level = LogLevel.INFO
}
install(HttpTimeout) {
requestTimeoutMillis = appSettingsService.getApiTimeout()
}
install(HttpCookies)
install(HttpRequestRetry) {
maxRetries = 2
retryIf { _, response ->
response.status == HttpStatusCode.Forbidden && api.shouldHavePostLogin() && api.hasLoginInfo()
}
modifyRequest {
Napier.i("Will modify", tag = "HttpSend")
CoroutineScope(Dispatchers.Main).launch {
Napier.i("Will login", tag = "HttpSend")
api.login()
Napier.i("Did login", tag = "HttpSend")
}
}
}
expectSuccess = false
}
class SelfossApi(private val appSettingsService: AppSettingsService) {
var client = createHttpClient()
private fun createHttpClient(): HttpClient {
val client = HttpClient {
install(ContentNegotiation) {
install(HttpCache)
json(Json {
prettyPrint = true
isLenient = true
ignoreUnknownKeys = true
explicitNulls = false
})
}
install(Logging) {
logger = object : Logger {
override fun log(message: String) {
Napier.d(message, tag = "LogApiCalls")
}
}
level = LogLevel.INFO
}
install(HttpTimeout) {
requestTimeoutMillis = appSettingsService.getApiTimeout()
}
install(HttpCookies)
install(HttpRequestRetry) {
maxRetries = 2
retryIf { _, response ->
response.status == HttpStatusCode.Forbidden && shouldHavePostLogin() && hasLoginInfo()
}
modifyRequest {
Napier.i("Will modify", tag = "HttpSend")
CoroutineScope(Dispatchers.Main).launch {
Napier.i("Will login", tag = "HttpSend")
this@SelfossApi.login()
Napier.i("Did login", tag = "HttpSend")
}
}
}
expectSuccess = false
}
return client
}
var client = createHttpClient(appSettingsService, this)
fun url(path: String) =
"${appSettingsService.getBaseUrl()}$path"
fun refreshLoginInformation() {
appSettingsService.refreshApiSettings()
client = createHttpClient()
client = createHttpClient(appSettingsService, this)
}
fun constructBasicAuthValue(credentials: BasicAuthCredentials): String {
@@ -88,8 +102,8 @@ 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() =
fun shouldHavePostLogin() = appSettingsService.getApiVersion() != -1
fun hasLoginInfo() =
appSettingsService.getUserName().isNotEmpty() && appSettingsService.getPassword()
.isNotEmpty()

View File

@@ -8,6 +8,7 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
// Api related
private var _apiVersion: Int = -1
private var _publicAccess: Boolean? = null
private var _selfSigned: Boolean? = null
private var _baseUrl: String = ""
private var _userName: String = ""
private var _basicUserName: String = ""
@@ -77,6 +78,22 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
_publicAccess = settings.getBoolean(API_PUBLIC_ACCESS, false)
}
fun getSelfSigned(): Boolean {
if (_selfSigned == null) {
refreshSelfSigned()
}
return _selfSigned!!
}
fun updateSelfSigned(selfSigned: Boolean) {
settings.putBoolean(API_SELF_SIGNED, selfSigned)
refreshSelfSigned()
}
private fun refreshSelfSigned() {
_selfSigned = settings.getBoolean(API_SELF_SIGNED, false)
}
fun getBaseUrl(): String {
if (_baseUrl.isEmpty()) {
refreshBaseUrl()
@@ -383,6 +400,7 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
refreshBaseUrl()
refreshApiVersion()
refreshPublicAccess()
refreshSelfSigned()
}
fun refreshUserSettings() {
@@ -468,6 +486,8 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
const val API_PUBLIC_ACCESS = "apiPublicAccess"
const val API_SELF_SIGNED = "apiSelfSigned"
const val API_ITEMS_NUMBER = "prefer_api_items_number"
const val API_TIMEOUT = "api_timeout"