Compare commits
	
		
			32 Commits
		
	
	
		
			v123051211
			...
			0aac2cba0b
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 0aac2cba0b | |||
| 42e1c1ebbd | |||
| a67a2a0927 | |||
| 24beee070c | |||
| 1e6bc57751 | |||
| 85d966f54b | |||
| 3fff8eeeb6 | |||
| 5eb5a5658f | |||
| 3646658cb1 | |||
| b793301706 | |||
| 313042955c | |||
| c4f4bafe85 | |||
| ed06b22a77 | |||
|  | 172362b533 | ||
|  | ad72cb6f56 | ||
|  | 9057ee0052 | ||
|  | 50d0b44315 | ||
|  | 21b08ed384 | ||
|  | 993c4d2ee9 | ||
|  | 57a9d51027 | ||
|  | 673f0edb8b | ||
|  | 7f96798f13 | ||
|  | 6e5704a45b | ||
|  | 495591159f | ||
|  | 718fe7c5ee | ||
|  | ecd23213f9 | ||
|  | e6baed8cb4 | ||
|  | c87abec0b9 | ||
|  | 0aba41d8bf | ||
|  | 2a2d1047b4 | ||
|  | 66ef1ccf32 | ||
|  | 677ede5bc7 | 
							
								
								
									
										55
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										55
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,3 +1,58 @@ | |||||||
|  | **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. | ||||||
|  | - Changelog for v123051301 [CI SKIP] | ||||||
|  |  | ||||||
|  | -------------------------------------------------------------------- | ||||||
|  |  | ||||||
|  | **v123051301** | ||||||
|  |  | ||||||
|  | - feat: Basic auth from url. Fixes #142 (#143) | ||||||
|  | - debug: Debug index out of bound exception. | ||||||
|  | - Changelog for v123051211 [CI SKIP] | ||||||
|  |  | ||||||
|  | -------------------------------------------------------------------- | ||||||
|  |  | ||||||
|  | **v123051211** | ||||||
|  |  | ||||||
|  | - fix: Sometimes url isn't even defined. | ||||||
|  | - Changelog for v123041021 [CI SKIP] | ||||||
|  |  | ||||||
|  | -------------------------------------------------------------------- | ||||||
|  |  | ||||||
| **v123041021** | **v123041021** | ||||||
|  |  | ||||||
| - fix: 'Enable Core Library Desugaring to support older Android versions' (#138) from davidoskky/ReaderForSelfoss-multiplatform:desugaring into master | - fix: 'Enable Core Library Desugaring to support older Android versions' (#138) from davidoskky/ReaderForSelfoss-multiplatform:desugaring into master | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ plugins { | |||||||
|     kotlin("android") |     kotlin("android") | ||||||
|     kotlin("kapt") |     kotlin("kapt") | ||||||
|     id("com.mikepenz.aboutlibraries.plugin") |     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 { | fun Project.execWithOutput(cmd: String, ignore: Boolean = false): String { | ||||||
| @@ -16,7 +16,7 @@ fun Project.execWithOutput(cmd: String, ignore: Boolean = false): String { | |||||||
|         project.exec { |         project.exec { | ||||||
|             commandLine = cmd.split(" ") |             commandLine = cmd.split(" ") | ||||||
|             standardOutput = outputStream |             standardOutput = outputStream | ||||||
|             isIgnoreExitValue = ignore ?: false |             isIgnoreExitValue = ignore | ||||||
|         } |         } | ||||||
|         outputStream.toString() |         outputStream.toString() | ||||||
|     } |     } | ||||||
| @@ -24,9 +24,8 @@ fun Project.execWithOutput(cmd: String, ignore: Boolean = false): String { | |||||||
| } | } | ||||||
|  |  | ||||||
| fun gitVersion(): String { | fun gitVersion(): String { | ||||||
|     var process = "" |  | ||||||
|     val maybeTagOfCurrentCommit = execWithOutput("git -C ../ describe --contains HEAD", true) |     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.") |         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") |         execWithOutput("git -C ../ for-each-ref refs/tags --sort=-refname --format='%(refname:short)' --count=1") | ||||||
|     } else { |     } else { | ||||||
| @@ -171,7 +170,7 @@ dependencies { | |||||||
|     //PhotoView |     //PhotoView | ||||||
|     implementation("com.github.chrisbanes:PhotoView:2.3.0") |     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") |     implementation("androidx.lifecycle:lifecycle-extensions:2.2.0") | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								androidApp/proguard-rules.pro
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								androidApp/proguard-rules.pro
									
									
									
									
										vendored
									
									
								
							| @@ -55,6 +55,7 @@ | |||||||
| # maybe remove later ? | # maybe remove later ? | ||||||
| -keep class * extends androidx.fragment.app.Fragment | -keep class * extends androidx.fragment.app.Fragment | ||||||
|  |  | ||||||
|  | -dontwarn org.slf4j.impl.StaticLoggerBinder | ||||||
|  |  | ||||||
| # Keep `Companion` object fields of serializable classes. | # Keep `Companion` object fields of serializable classes. | ||||||
| # This avoids serializer lookup through `getDeclaredClasses` as done for named companion objects. | # This avoids serializer lookup through `getDeclaredClasses` as done for named companion objects. | ||||||
|   | |||||||
| @@ -599,12 +599,14 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar | |||||||
|                 return true |                 return true | ||||||
|             } |             } | ||||||
|             R.id.action_disconnect -> { |             R.id.action_disconnect -> { | ||||||
|  |                 needsConfirmation(R.string.confirm_disconnect_title, R.string.confirm_disconnect_description) { | ||||||
|                     runBlocking { |                     runBlocking { | ||||||
|                         repository.logout() |                         repository.logout() | ||||||
|                     } |                     } | ||||||
|                     val intent = Intent(this, LoginActivity::class.java) |                     val intent = Intent(this, LoginActivity::class.java) | ||||||
|                     this.startActivity(intent) |                     this.startActivity(intent) | ||||||
|                     finish() |                     finish() | ||||||
|  |                 } | ||||||
|                 return true |                 return true | ||||||
|             } |             } | ||||||
|             R.id.action_settings -> { |             R.id.action_settings -> { | ||||||
|   | |||||||
| @@ -139,9 +139,12 @@ class LoginActivity : AppCompatActivity(), DIAware { | |||||||
|  |  | ||||||
|         showProgress(true) |         showProgress(true) | ||||||
|  |  | ||||||
|  |         appSettingsService.updateSelfSigned(binding.selfSigned.isChecked) | ||||||
|  |  | ||||||
|         repository.refreshLoginInformation(url, login, password) |         repository.refreshLoginInformation(url, login, password) | ||||||
|  |  | ||||||
|         CoroutineScope(Dispatchers.Main).launch { |         CoroutineScope(Dispatchers.Main).launch { | ||||||
|  |             repository.updateApiInformation() | ||||||
|             val result = repository.login() |             val result = repository.login() | ||||||
|             if (result) { |             if (result) { | ||||||
|                 val (errorFetching, displaySelfossOnly) = repository.shouldBeSelfossInstance() |                 val (errorFetching, displaySelfossOnly) = repository.shouldBeSelfossInstance() | ||||||
|   | |||||||
| @@ -71,7 +71,11 @@ class ReaderActivity : AppCompatActivity(), DIAware { | |||||||
|             finish() |             finish() | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         try { | ||||||
|             readItem(allItems[currentItem]) |             readItem(allItems[currentItem]) | ||||||
|  |         } catch (e: IndexOutOfBoundsException) { | ||||||
|  |             finish() | ||||||
|  |         } | ||||||
|  |  | ||||||
|         binding.pager.adapter = ScreenSlidePagerAdapter(this) |         binding.pager.adapter = ScreenSlidePagerAdapter(this) | ||||||
|         binding.pager.setCurrentItem(currentItem, false) |         binding.pager.setCurrentItem(currentItem, false) | ||||||
|   | |||||||
| @@ -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.model.toParcelable | ||||||
| import bou.amine.apps.readerforselfossv2.android.sendSilentlyWithAcraWithName | import bou.amine.apps.readerforselfossv2.android.sendSilentlyWithAcraWithName | ||||||
| import bou.amine.apps.readerforselfossv2.android.utils.glide.getBitmapInputStream | 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.openInBrowserAsNewTask | ||||||
| import bou.amine.apps.readerforselfossv2.android.utils.shareLink | import bou.amine.apps.readerforselfossv2.android.utils.shareLink | ||||||
| import bou.amine.apps.readerforselfossv2.model.MercuryModel | import bou.amine.apps.readerforselfossv2.model.MercuryModel | ||||||
| @@ -314,7 +315,7 @@ class ArticleFragment : Fragment(), DIAware { | |||||||
|         binding.webcontent.webViewClient = object : WebViewClient() { |         binding.webcontent.webViewClient = object : WebViewClient() { | ||||||
|             @Deprecated("Deprecated in Java") |             @Deprecated("Deprecated in Java") | ||||||
|             override fun shouldOverrideUrlLoading(view: WebView?, url: String): Boolean { |             override fun shouldOverrideUrlLoading(view: WebView?, url: String): Boolean { | ||||||
|                 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 { |                     try { | ||||||
|                         requireContext().startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) |                         requireContext().startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) | ||||||
|                     } catch (e: ActivityNotFoundException) { |                     } catch (e: ActivityNotFoundException) { | ||||||
| @@ -530,7 +531,12 @@ class ArticleFragment : Fragment(), DIAware { | |||||||
|  |  | ||||||
|     private fun openInBrowserAfterFailing() { |     private fun openInBrowserAfterFailing() { | ||||||
|         binding.progressBar.visibility = View.GONE |         binding.progressBar.visibility = View.GONE | ||||||
|         requireActivity().openInBrowserAsNewTask(this@ArticleFragment.item) |         if (context != null) { | ||||||
|  |             requireContext().openInBrowserAsNewTask(this@ArticleFragment.item) | ||||||
|  |         } else { | ||||||
|  |             Exception("openInBrowserAfterFailing context is null").sendSilentlyWithAcraWithName("openInBrowserAfterFailing > $context") | ||||||
|  |  | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     companion object { |     companion object { | ||||||
| @@ -548,8 +554,8 @@ class ArticleFragment : Fragment(), DIAware { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun performClick(): Boolean { |     fun performClick(): Boolean { | ||||||
|         if (binding.webcontent.hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE || |         if (allImages != null && (binding.webcontent.hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE || | ||||||
|             binding.webcontent.hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE |             binding.webcontent.hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) | ||||||
|         ) { |         ) { | ||||||
|  |  | ||||||
|             val position: Int = allImages.indexOf(binding.webcontent.hitTestResult.extra) |             val position: Int = allImages.indexOf(binding.webcontent.hitTestResult.extra) | ||||||
|   | |||||||
| @@ -82,7 +82,7 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware { | |||||||
|     ) { |     ) { | ||||||
|         val sourceGroup = binding.sourcesGroup |         val sourceGroup = binding.sourcesGroup | ||||||
|  |  | ||||||
|         repository.getSourcesDetailsOrStats().forEach { source -> |         repository.getSourcesDetailsOrStats().forEachIndexed { _, source -> | ||||||
|             val c = Chip(context) |             val c = Chip(context) | ||||||
|             c.ellipsize = TextUtils.TruncateAt.END |             c.ellipsize = TextUtils.TruncateAt.END | ||||||
|  |  | ||||||
| @@ -144,11 +144,12 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware { | |||||||
|  |  | ||||||
|         val tags = repository.getTags() |         val tags = repository.getTags() | ||||||
|  |  | ||||||
|         tags.forEach { tag -> |         tags.forEachIndexed { _, tag -> | ||||||
|             val c = Chip(context) |             val c = Chip(context) | ||||||
|             c.ellipsize = TextUtils.TruncateAt.END |             c.ellipsize = TextUtils.TruncateAt.END | ||||||
|             c.text = tag.tag |             c.text = tag.tag | ||||||
|  |  | ||||||
|  |             if (tag.color.isNotEmpty()) { | ||||||
|                 try { |                 try { | ||||||
|                     val gd = GradientDrawable() |                     val gd = GradientDrawable() | ||||||
|                     val gdColor = try { |                     val gdColor = try { | ||||||
| @@ -165,6 +166,7 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware { | |||||||
|                 } catch (e: Exception) { |                 } catch (e: Exception) { | ||||||
|                     e.sendSilentlyWithAcraWithName("tags > GradientDrawable") |                     e.sendSilentlyWithAcraWithName("tags > GradientDrawable") | ||||||
|                 } |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|             c.setOnCloseIconClickListener { |             c.setOnCloseIconClickListener { | ||||||
|                 (it as Chip).isCloseIconVisible = false |                 (it as Chip).isCloseIconVisible = false | ||||||
|   | |||||||
| @@ -51,6 +51,13 @@ | |||||||
|                 android:maxLines="1" |                 android:maxLines="1" | ||||||
|                 android:minHeight="48dp" /> |                 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 |             <com.google.android.material.switchmaterial.SwitchMaterial | ||||||
|                 android:id="@+id/withLogin" |                 android:id="@+id/withLogin" | ||||||
|                 android:layout_width="match_parent" |                 android:layout_width="match_parent" | ||||||
|   | |||||||
| @@ -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="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="menu_home_sources">Sources</string> | ||||||
|     <string name="update_source">Update source</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> | </resources> | ||||||
|   | |||||||
| @@ -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="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="menu_home_sources">Sources</string> | ||||||
|     <string name="update_source">Update source</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> | </resources> | ||||||
|   | |||||||
| @@ -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="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="menu_home_sources">Sources</string> | ||||||
|     <string name="update_source">Update source</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> | </resources> | ||||||
|   | |||||||
| @@ -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="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="menu_home_sources">Sources</string> | ||||||
|     <string name="update_source">Update source</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> | </resources> | ||||||
|   | |||||||
| @@ -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="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="menu_home_sources">Sources</string> | ||||||
|     <string name="update_source">Update source</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> | </resources> | ||||||
|   | |||||||
| @@ -33,8 +33,8 @@ | |||||||
|     <string name="addStringNoUrl">"Accede pra engadir fontes."</string> |     <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_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_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_no_network">"Non se pode obter a lista de spouts por mor dun erro de rede."</string> | ||||||
|     <string name="cant_get_spouts">"Can't get spouts list. There may ben an api issue."</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="form_not_complete">"O formulario non está completo"</string> | ||||||
|     <string name="pref_header_links">"Ligazóns"</string> |     <string name="pref_header_links">"Ligazóns"</string> | ||||||
|     <string name="issue_tracker_link">"Rastrexador de Incidencias"</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_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="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="remove_source">Eliminar fonte</string> | ||||||
|     <string name="pref_theme_title">Light/Dark mode</string> |     <string name="pref_theme_title">Modo Claro/Escuro</string> | ||||||
|     <string name="mode_dark">Dark mode</string> |     <string name="mode_dark">Modo escuro</string> | ||||||
|     <string name="mode_system">Follow the system setting</string> |     <string name="mode_system">Seguir axustes do sistema</string> | ||||||
|     <string name="mode_light">Light mode</string> |     <string name="mode_light">Modo claro</string> | ||||||
|     <string name="gdpr_dialog_title">The app does not share any personal data about you.</string> |     <string name="gdpr_dialog_title">A aplicación non comparte ningún dato persoal seu.</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="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">A crash occured. Sending the details to the developper.</string> |     <string name="crash_toast_text">Ocurriu un erro. Enviando os detalles o desenvolvedor.</string> | ||||||
|     <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> |     <string name="pref_switch_disable_acra">"Deshabilitar o reporte automático de erros. "</string> | ||||||
|     <string name="menu_home_filter">Filters</string> |     <string name="menu_home_filter">Filtros</string> | ||||||
|     <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</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="menu_home_sources">Sources</string> | ||||||
|     <string name="update_source">Update source</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> | </resources> | ||||||
|   | |||||||
| @@ -90,7 +90,7 @@ | |||||||
|     <string name="pref_switch_items_caching">Save items for offline use</string> |     <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">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="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="network_connectivity_retrieved">"Network connection is now available"</string> | ||||||
|     <string name="pref_switch_periodic_refresh">Sync articles</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> |     <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="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="menu_home_sources">Sources</string> | ||||||
|     <string name="update_source">Update source</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> | </resources> | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> | <?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="app_name">"Lettore RSS per Selfoss"</string> | ||||||
|     <string name="title_activity_login">"Accedi"</string> |     <string name="title_activity_login">"Accedi"</string> | ||||||
|     <string name="prompt_password">"Password"</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="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="menu_home_sources">Sources</string> | ||||||
|     <string name="update_source">Update source</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> | </resources> | ||||||
|   | |||||||
| @@ -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="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="menu_home_sources">Sources</string> | ||||||
|     <string name="update_source">Update source</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> | </resources> | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> | <?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_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="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="crash_toast_text">A crash occured. Sending the details to the developper.</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="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="menu_home_sources">Sources</string> | ||||||
|     <string name="update_source">Update source</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> | </resources> | ||||||
|   | |||||||
| @@ -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="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="menu_home_sources">Sources</string> | ||||||
|     <string name="update_source">Update source</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> | </resources> | ||||||
|   | |||||||
| @@ -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="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="menu_home_sources">Sources</string> | ||||||
|     <string name="update_source">Update source</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> | </resources> | ||||||
|   | |||||||
| @@ -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="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="menu_home_sources">Sources</string> | ||||||
|     <string name="update_source">Update source</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> | </resources> | ||||||
|   | |||||||
| @@ -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="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="menu_home_sources">Sources</string> | ||||||
|     <string name="update_source">Update source</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> | </resources> | ||||||
|   | |||||||
| @@ -125,7 +125,10 @@ | |||||||
|     <string name="crash_toast_text">发生崩溃。请将细节发送给开发人员。</string> |     <string name="crash_toast_text">发生崩溃。请将细节发送给开发人员。</string> | ||||||
|     <string name="pref_switch_disable_acra">"禁用自动错误报告 "</string> |     <string name="pref_switch_disable_acra">"禁用自动错误报告 "</string> | ||||||
|     <string name="menu_home_filter">筛选器</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="application_selfoss_only">此应用只适用于 Selfoss 实例,不适用于其他 RSS 。</string> | ||||||
|     <string name="menu_home_sources">Sources</string> |     <string name="menu_home_sources">源</string> | ||||||
|     <string name="update_source">Update source</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> | </resources> | ||||||
|   | |||||||
| @@ -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="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="menu_home_sources">Sources</string> | ||||||
|     <string name="update_source">Update source</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> | </resources> | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ | |||||||
|     <string name="error_invalid_password">"Password not long enough"</string> |     <string name="error_invalid_password">"Password not long enough"</string> | ||||||
|     <string name="error_field_required">"Field required"</string> |     <string name="error_field_required">"Field required"</string> | ||||||
|     <string name="prompt_url">"Url"</string> |     <string name="prompt_url">"Url"</string> | ||||||
|  |     <string name="disable_ssl">"Disable SSL"</string> | ||||||
|     <string name="withLoginSwitch">"Login required ?"</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="login_url_problem">"Oops. You may need to add a \"/\" at the end of the url."</string> | ||||||
|     <string name="prompt_login">"Username"</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="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="menu_home_sources">Sources</string> | ||||||
|     <string name="update_source">Update source</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> | </resources> | ||||||
|   | |||||||
| @@ -7,12 +7,12 @@ buildscript { | |||||||
|  |  | ||||||
| plugins { | plugins { | ||||||
|     //trick: for the same plugin versions in all sub-modules |     //trick: for the same plugin versions in all sub-modules | ||||||
|     id("com.android.application").version("7.4.0").apply(false) |     id("com.android.application").version("8.1.0").apply(false) | ||||||
|     id("com.android.library").version("7.4.0").apply(false) |     id("com.android.library").version("8.1.0").apply(false) | ||||||
|     kotlin("android").version("1.7.20").apply(false) |     id("org.jetbrains.kotlin.android").version("1.9.10").apply(false) | ||||||
|     kotlin("multiplatform").version("1.7.20").apply(false) |     kotlin("multiplatform").version("1.9.10").apply(false) | ||||||
|     id("com.mikepenz.aboutlibraries.plugin").version("10.5.1").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 { | allprojects { | ||||||
|   | |||||||
| @@ -13,22 +13,15 @@ | |||||||
| #Tue Mar 22 16:50:00 CET 2022 | #Tue Mar 22 16:50:00 CET 2022 | ||||||
| #Gradle | #Gradle | ||||||
| org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M" | org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M" | ||||||
|  |  | ||||||
| #Kotlin | #Kotlin | ||||||
| kotlin.code.style=official | kotlin.code.style=official | ||||||
|  |  | ||||||
| #Android | #Android | ||||||
| android.useAndroidX=true | android.useAndroidX=true | ||||||
| kotlin.native.enableDependencyPropagation=false |  | ||||||
| #android.nonTransitiveRClass=true | #android.nonTransitiveRClass=true | ||||||
| android.enableJetifier=true | android.enableJetifier=true | ||||||
|  | android.nonTransitiveRClass=false | ||||||
|  |  | ||||||
| #MPP | #MPP | ||||||
| kotlin.mpp.enableCInteropCommonization=true | kotlin.mpp.enableCInteropCommonization=true | ||||||
| kotlin.mpp.enableGranularSourceSetsMetadata=true |  | ||||||
|  |  | ||||||
|  |  | ||||||
| org.gradle.parallel=true | org.gradle.parallel=true | ||||||
| org.gradle.caching=true | org.gradle.caching=true | ||||||
| ignoreGitVersion=false | ignoreGitVersion=false | ||||||
|   | |||||||
							
								
								
									
										6
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							| @@ -1,6 +1,6 @@ | |||||||
| #Mon Jan 23 20:47:46 CET 2023 | #Thu Jul 13 11:41:19 CEST 2023 | ||||||
| distributionBase=GRADLE_USER_HOME | distributionBase=GRADLE_USER_HOME | ||||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip |  | ||||||
| distributionPath=wrapper/dists | distributionPath=wrapper/dists | ||||||
| zipStorePath=wrapper/dists | distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip | ||||||
| zipStoreBase=GRADLE_USER_HOME | zipStoreBase=GRADLE_USER_HOME | ||||||
|  | zipStorePath=wrapper/dists | ||||||
|   | |||||||
| @@ -1,3 +1,5 @@ | |||||||
|  | val ktorVersion = "2.3.2" | ||||||
|  |  | ||||||
| object SqlDelight { | object SqlDelight { | ||||||
|     const val runtime = "com.squareup.sqldelight:runtime:1.5.4" |     const val runtime = "com.squareup.sqldelight:runtime:1.5.4" | ||||||
|     const val android = "com.squareup.sqldelight:android-driver:1.5.4" |     const val android = "com.squareup.sqldelight:android-driver:1.5.4" | ||||||
| @@ -9,12 +11,13 @@ plugins { | |||||||
|     kotlin("multiplatform") |     kotlin("multiplatform") | ||||||
|     id("com.android.library") |     id("com.android.library") | ||||||
|     id("com.squareup.sqldelight") |     id("com.squareup.sqldelight") | ||||||
|     kotlin("plugin.serialization") version "1.4.10" |     kotlin("plugin.serialization") version "1.9.0" | ||||||
|     id("org.jetbrains.kotlinx.kover") version "0.6.1" |     id("org.jetbrains.kotlinx.kover") | ||||||
| } | } | ||||||
|  |  | ||||||
| kotlin { | kotlin { | ||||||
|     android() |     androidTarget() | ||||||
|  |     jvmToolchain(11) | ||||||
|  |  | ||||||
|     listOf( |     listOf( | ||||||
|         iosX64(), |         iosX64(), | ||||||
| @@ -29,13 +32,15 @@ kotlin { | |||||||
|     sourceSets { |     sourceSets { | ||||||
|         val commonMain by getting { |         val commonMain by getting { | ||||||
|             dependencies { |             dependencies { | ||||||
|                 implementation("io.ktor:ktor-client-core:2.1.1") |                 implementation("io.ktor:ktor-client-core:$ktorVersion") | ||||||
|                 implementation("io.ktor:ktor-client-content-negotiation:2.1.1") |                 implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion") | ||||||
|                 implementation("io.ktor:ktor-serialization-kotlinx-json:2.1.1") |                 implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion") | ||||||
|                 implementation("io.ktor:ktor-client-logging:2.1.1") |                 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("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 |                 //Dependency Injection | ||||||
|                 implementation("org.kodein.di:kodein-di:7.12.0") |                 implementation("org.kodein.di:kodein-di:7.12.0") | ||||||
| @@ -58,14 +63,14 @@ kotlin { | |||||||
|         } |         } | ||||||
|         val androidMain by getting { |         val androidMain by getting { | ||||||
|             dependencies { |             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") |                 implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0") | ||||||
|  |  | ||||||
|                 // Sql |                 // Sql | ||||||
|                 implementation(SqlDelight.android) |                 implementation(SqlDelight.android) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         val androidTest by getting { |         val androidUnitTest by getting { | ||||||
|             dependencies { |             dependencies { | ||||||
|                 implementation(kotlin("test-junit")) |                 implementation(kotlin("test-junit")) | ||||||
|                 implementation("junit:junit:4.13.2") |                 implementation("junit:junit:4.13.2") | ||||||
| @@ -98,15 +103,14 @@ kotlin { | |||||||
| } | } | ||||||
|  |  | ||||||
| android { | android { | ||||||
|     compileSdk = 32 |     compileSdk = 33 | ||||||
|     sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") |     sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") | ||||||
|     defaultConfig { |     defaultConfig { | ||||||
|         minSdk = 21 |         minSdk = 21 | ||||||
|         targetSdk = 32 |  | ||||||
|     } |     } | ||||||
|     compileOptions { |     compileOptions { | ||||||
|         sourceCompatibility = JavaVersion.VERSION_1_8 |         sourceCompatibility = JavaVersion.VERSION_11 | ||||||
|         targetCompatibility = JavaVersion.VERSION_1_8 |         targetCompatibility = JavaVersion.VERSION_11 | ||||||
|     } |     } | ||||||
|     namespace = "bou.amine.apps.readerforselfossv2" |     namespace = "bou.amine.apps.readerforselfossv2" | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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() | ||||||
|  | } | ||||||
| @@ -6,12 +6,12 @@ class MercuryModel { | |||||||
|  |  | ||||||
|     @Serializable |     @Serializable | ||||||
|     class ParsedContent( |     class ParsedContent( | ||||||
|         val title: String?, |         val title: String? = null, | ||||||
|         val content: String?, |         val content: String? = null, | ||||||
|         val lead_image_url: String?, // NOSONAR |         val lead_image_url: String? = null, // NOSONAR | ||||||
|         val url: String?, |         val url: String? = null, | ||||||
|         val error: Boolean?, |         val error: Boolean? = null, | ||||||
|         val message: String?, |         val message: String? = null, | ||||||
|         val failed: Boolean? |         val failed: Boolean? = null | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -24,8 +24,8 @@ class SelfossModel { | |||||||
|     @Serializable |     @Serializable | ||||||
|     class Stats( |     class Stats( | ||||||
|         val total: Int, |         val total: Int, | ||||||
|         val unread: Int?, |         val unread: Int? = null, | ||||||
|         val starred: Int? |         val starred: Int? = null | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     @Serializable |     @Serializable | ||||||
| @@ -36,9 +36,9 @@ class SelfossModel { | |||||||
|  |  | ||||||
|     @Serializable |     @Serializable | ||||||
|     data class ApiInformation( |     data class ApiInformation( | ||||||
|         val version: String?, |         val version: String? = null, | ||||||
|         val apiversion: String?, |         val apiversion: String? = null, | ||||||
|         val configuration: ApiConfiguration? |         val configuration: ApiConfiguration? = null | ||||||
|     ) { |     ) { | ||||||
|         fun getApiMajorVersion(): Int { |         fun getApiMajorVersion(): Int { | ||||||
|             var versionNumber = 0 |             var versionNumber = 0 | ||||||
| @@ -54,9 +54,9 @@ class SelfossModel { | |||||||
|     @Serializable |     @Serializable | ||||||
|     data class ApiConfiguration( |     data class ApiConfiguration( | ||||||
|         @Serializable(with = BooleanSerializer::class) |         @Serializable(with = BooleanSerializer::class) | ||||||
|         val publicMode: Boolean?, |         val publicMode: Boolean? = null, | ||||||
|         @Serializable(with = BooleanSerializer::class) |         @Serializable(with = BooleanSerializer::class) | ||||||
|         val authEnabled: Boolean? |         val authEnabled: Boolean? = null | ||||||
|     ) { |     ) { | ||||||
|         fun isAuthEnabled() = authEnabled ?: true |         fun isAuthEnabled() = authEnabled ?: true | ||||||
|  |  | ||||||
| @@ -75,7 +75,7 @@ class SelfossModel { | |||||||
|     data class SourceStats( |     data class SourceStats( | ||||||
|         override val id: Int, |         override val id: Int, | ||||||
|         override var title: String, |         override var title: String, | ||||||
|         override var unread: Int?, |         override var unread: Int? = null, | ||||||
|         override var error: String? = null, |         override var error: String? = null, | ||||||
|         override var icon: String? = null |         override var icon: String? = null | ||||||
|         ) : Source |         ) : Source | ||||||
| @@ -86,11 +86,11 @@ class SelfossModel { | |||||||
|         override var title: String, |         override var title: String, | ||||||
|         override var unread: Int? = null, |         override var unread: Int? = null, | ||||||
|         @Serializable(with = TagsListSerializer::class) |         @Serializable(with = TagsListSerializer::class) | ||||||
|         var tags: List<String>?, |         var tags: List<String>? = null, | ||||||
|         var spout: String?, |         var spout: String? = null, | ||||||
|         override var error: String?, |         override var error: String? = null, | ||||||
|         override var icon: String?, |         override var icon: String? = null, | ||||||
|         var params: SourceParams? |         var params: SourceParams? = null | ||||||
|     ) : Source |     ) : Source | ||||||
|  |  | ||||||
|     @Serializable |     @Serializable | ||||||
| @@ -107,13 +107,13 @@ class SelfossModel { | |||||||
|         var unread: Boolean, |         var unread: Boolean, | ||||||
|         @Serializable(with = BooleanSerializer::class) |         @Serializable(with = BooleanSerializer::class) | ||||||
|         var starred: Boolean, |         var starred: Boolean, | ||||||
|         val thumbnail: String?, |         val thumbnail: String? = null, | ||||||
|         val icon: String?, |         val icon: String? = null, | ||||||
|         val link: String, |         val link: String, | ||||||
|         val sourcetitle: String, |         val sourcetitle: String, | ||||||
|         @Serializable(with = TagsListSerializer::class) |         @Serializable(with = TagsListSerializer::class) | ||||||
|         val tags: List<String>, |         val tags: List<String>, | ||||||
|         val author: String? |         val author: String? = null | ||||||
|     ) { |     ) { | ||||||
|         fun getLinkDecoded(): String { |         fun getLinkDecoded(): String { | ||||||
|             var stringUrl: String |             var stringUrl: String | ||||||
|   | |||||||
| @@ -5,27 +5,46 @@ import bou.amine.apps.readerforselfossv2.model.StatusAndData | |||||||
| import bou.amine.apps.readerforselfossv2.model.SuccessResponse | import bou.amine.apps.readerforselfossv2.model.SuccessResponse | ||||||
| import bou.amine.apps.readerforselfossv2.service.AppSettingsService | import bou.amine.apps.readerforselfossv2.service.AppSettingsService | ||||||
| import io.github.aakira.napier.Napier | import io.github.aakira.napier.Napier | ||||||
| import io.ktor.client.* | import io.ktor.client.HttpClient | ||||||
| import io.ktor.client.plugins.* | import io.ktor.client.engine.cio.CIO | ||||||
| import io.ktor.client.plugins.cache.* | import io.ktor.client.engine.cio.CIOEngineConfig | ||||||
| import io.ktor.client.plugins.contentnegotiation.* | import io.ktor.client.plugins.HttpRequestRetry | ||||||
| import io.ktor.client.plugins.cookies.* | import io.ktor.client.plugins.HttpTimeout | ||||||
| import io.ktor.client.plugins.logging.* | import io.ktor.client.plugins.auth.providers.BasicAuthCredentials | ||||||
| import io.ktor.client.request.* | import io.ktor.client.plugins.cache.HttpCache | ||||||
| import io.ktor.client.statement.* | import io.ktor.client.plugins.contentnegotiation.ContentNegotiation | ||||||
| import io.ktor.http.* | import io.ktor.client.plugins.cookies.HttpCookies | ||||||
| import io.ktor.serialization.kotlinx.json.* | 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.CoroutineScope | ||||||
| import kotlinx.coroutines.Dispatchers | import kotlinx.coroutines.Dispatchers | ||||||
| import kotlinx.coroutines.launch | import kotlinx.coroutines.launch | ||||||
| import kotlinx.serialization.json.Json | import kotlinx.serialization.json.Json | ||||||
|  |  | ||||||
| class SelfossApi(private val appSettingsService: AppSettingsService) { | expect fun setupInsecureHTTPEngine(config: CIOEngineConfig) | ||||||
|  |  | ||||||
|     var client = createHttpClient() | fun createHttpClient( | ||||||
|  |     appSettingsService: AppSettingsService, | ||||||
|     private fun createHttpClient(): HttpClient { |     api: SelfossApi | ||||||
|         val client = HttpClient { | ) = | ||||||
|  |     HttpClient(CIO) { | ||||||
|  |         if (appSettingsService.getSelfSigned()) { | ||||||
|  |             engine { | ||||||
|  |                 setupInsecureHTTPEngine(this) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|         install(ContentNegotiation) { |         install(ContentNegotiation) { | ||||||
|             install(HttpCache) |             install(HttpCache) | ||||||
|             json(Json { |             json(Json { | ||||||
| @@ -49,13 +68,13 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | |||||||
|         install(HttpRequestRetry) { |         install(HttpRequestRetry) { | ||||||
|             maxRetries = 2 |             maxRetries = 2 | ||||||
|             retryIf { _, response -> |             retryIf { _, response -> | ||||||
|                     response.status == HttpStatusCode.Forbidden && shouldHavePostLogin() && hasLoginInfo() |                 response.status == HttpStatusCode.Forbidden && api.shouldHavePostLogin() && api.hasLoginInfo() | ||||||
|             } |             } | ||||||
|             modifyRequest { |             modifyRequest { | ||||||
|                 Napier.i("Will modify", tag = "HttpSend") |                 Napier.i("Will modify", tag = "HttpSend") | ||||||
|                 CoroutineScope(Dispatchers.Main).launch { |                 CoroutineScope(Dispatchers.Main).launch { | ||||||
|                     Napier.i("Will login", tag = "HttpSend") |                     Napier.i("Will login", tag = "HttpSend") | ||||||
|                         this@SelfossApi.login() |                     api.login() | ||||||
|                     Napier.i("Did login", tag = "HttpSend") |                     Napier.i("Did login", tag = "HttpSend") | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| @@ -63,20 +82,28 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | |||||||
|         expectSuccess = false |         expectSuccess = false | ||||||
|     } |     } | ||||||
|  |  | ||||||
|         return client | class SelfossApi(private val appSettingsService: AppSettingsService) { | ||||||
|     } |  | ||||||
|  |     var client = createHttpClient(appSettingsService, this) | ||||||
|  |  | ||||||
|     fun url(path: String) = |     fun url(path: String) = | ||||||
|         "${appSettingsService.getBaseUrl()}$path" |         "${appSettingsService.getBaseUrl()}$path" | ||||||
|  |  | ||||||
|     fun refreshLoginInformation() { |     fun refreshLoginInformation() { | ||||||
|         appSettingsService.refreshApiSettings() |         appSettingsService.refreshApiSettings() | ||||||
|         client = createHttpClient() |         client = createHttpClient(appSettingsService, this) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun constructBasicAuthValue(credentials: BasicAuthCredentials): String { | ||||||
|  |         val authString = "${credentials.username}:${credentials.password}" | ||||||
|  |         val authBuf = authString.toByteArray(Charsets.UTF_8).encodeBase64() | ||||||
|  |  | ||||||
|  |         return "Basic $authBuf" | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Api version was introduces after the POST login, so when there is a version, it should be available |     // Api version was introduces after the POST login, so when there is a version, it should be available | ||||||
|     private fun shouldHavePostLogin() = appSettingsService.getApiVersion() != -1 |     fun shouldHavePostLogin() = appSettingsService.getApiVersion() != -1 | ||||||
|     private fun hasLoginInfo() = |     fun hasLoginInfo() = | ||||||
|         appSettingsService.getUserName().isNotEmpty() && appSettingsService.getPassword() |         appSettingsService.getUserName().isNotEmpty() && appSettingsService.getPassword() | ||||||
|             .isNotEmpty() |             .isNotEmpty() | ||||||
|  |  | ||||||
| @@ -96,11 +123,23 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | |||||||
|     private suspend fun getLogin() = maybeResponse(client.tryToGet(url("/login")) { |     private suspend fun getLogin() = maybeResponse(client.tryToGet(url("/login")) { | ||||||
|         parameter("username", appSettingsService.getUserName()) |         parameter("username", appSettingsService.getUserName()) | ||||||
|         parameter("password", appSettingsService.getPassword()) |         parameter("password", appSettingsService.getPassword()) | ||||||
|  |         if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||||
|  |             headers { | ||||||
|  |                 append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword())) | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     private suspend fun postLogin() = maybeResponse(client.tryToPost(url("/login")) { |     private suspend fun postLogin() = maybeResponse(client.tryToPost(url("/login")) { | ||||||
|         parameter("username", appSettingsService.getUserName()) |         parameter("username", appSettingsService.getUserName()) | ||||||
|         parameter("password", appSettingsService.getPassword()) |         parameter("password", appSettingsService.getPassword()) | ||||||
|  |         if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||||
|  |             headers { | ||||||
|  |                 append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword())) | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     private fun shouldHaveNewLogout() = |     private fun shouldHaveNewLogout() = | ||||||
| @@ -114,9 +153,23 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|     private suspend fun maybeLogoutIfAvailable() = |     private suspend fun maybeLogoutIfAvailable() = | ||||||
|         responseOrSuccessIf404(client.tryToGet(url("/logout"))) |         responseOrSuccessIf404(client.tryToGet(url("/logout")) { | ||||||
|  |             if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||||
|  |                 headers { | ||||||
|  |                     append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword())) | ||||||
|  |                     ) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }) | ||||||
|  |  | ||||||
|     private suspend fun doLogout() = maybeResponse(client.tryToDelete(url("/api/session/current"))) |     private suspend fun doLogout() = maybeResponse(client.tryToDelete(url("/api/session/current")) { | ||||||
|  |         if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||||
|  |             headers { | ||||||
|  |                 append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword())) | ||||||
|  |                 ) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     }) | ||||||
|  |  | ||||||
|     suspend fun getItems( |     suspend fun getItems( | ||||||
|         type: String, |         type: String, | ||||||
| @@ -139,6 +192,12 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | |||||||
|             parameter("updatedsince", updatedSince) |             parameter("updatedsince", updatedSince) | ||||||
|             parameter("items", items ?: appSettingsService.getItemsNumber()) |             parameter("items", items ?: appSettingsService.getItemsNumber()) | ||||||
|             parameter("offset", offset) |             parameter("offset", offset) | ||||||
|  |             if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||||
|  |                 headers { | ||||||
|  |                     append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword())) | ||||||
|  |                     ) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|         }) |         }) | ||||||
|  |  | ||||||
|     suspend fun getItemsWithoutCatch(): StatusAndData<List<SelfossModel.Item>> = |     suspend fun getItemsWithoutCatch(): StatusAndData<List<SelfossModel.Item>> = | ||||||
| @@ -149,6 +208,12 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | |||||||
|             } |             } | ||||||
|             parameter("type", "all") |             parameter("type", "all") | ||||||
|             parameter("items", 1) |             parameter("items", 1) | ||||||
|  |             if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||||
|  |                 headers { | ||||||
|  |                     append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword())) | ||||||
|  |                     ) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|         }) |         }) | ||||||
|  |  | ||||||
|     suspend fun stats(): StatusAndData<SelfossModel.Stats> = |     suspend fun stats(): StatusAndData<SelfossModel.Stats> = | ||||||
| @@ -157,6 +222,12 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | |||||||
|                 parameter("username", appSettingsService.getUserName()) |                 parameter("username", appSettingsService.getUserName()) | ||||||
|                 parameter("password", appSettingsService.getPassword()) |                 parameter("password", appSettingsService.getPassword()) | ||||||
|             } |             } | ||||||
|  |             if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||||
|  |                 headers { | ||||||
|  |                     append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword())) | ||||||
|  |                     ) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|         }) |         }) | ||||||
|  |  | ||||||
|     suspend fun tags(): StatusAndData<List<SelfossModel.Tag>> = |     suspend fun tags(): StatusAndData<List<SelfossModel.Tag>> = | ||||||
| @@ -165,6 +236,12 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | |||||||
|                 parameter("username", appSettingsService.getUserName()) |                 parameter("username", appSettingsService.getUserName()) | ||||||
|                 parameter("password", appSettingsService.getPassword()) |                 parameter("password", appSettingsService.getPassword()) | ||||||
|             } |             } | ||||||
|  |             if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||||
|  |                 headers { | ||||||
|  |                     append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword())) | ||||||
|  |                     ) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|         }) |         }) | ||||||
|  |  | ||||||
|     suspend fun update(): StatusAndData<String> = |     suspend fun update(): StatusAndData<String> = | ||||||
| @@ -173,6 +250,12 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | |||||||
|                 parameter("username", appSettingsService.getUserName()) |                 parameter("username", appSettingsService.getUserName()) | ||||||
|                 parameter("password", appSettingsService.getPassword()) |                 parameter("password", appSettingsService.getPassword()) | ||||||
|             } |             } | ||||||
|  |             if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||||
|  |                 headers { | ||||||
|  |                     append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword())) | ||||||
|  |                     ) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|         }) |         }) | ||||||
|  |  | ||||||
|     suspend fun spouts(): StatusAndData<Map<String, SelfossModel.Spout>> = |     suspend fun spouts(): StatusAndData<Map<String, SelfossModel.Spout>> = | ||||||
| @@ -181,6 +264,12 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | |||||||
|                 parameter("username", appSettingsService.getUserName()) |                 parameter("username", appSettingsService.getUserName()) | ||||||
|                 parameter("password", appSettingsService.getPassword()) |                 parameter("password", appSettingsService.getPassword()) | ||||||
|             } |             } | ||||||
|  |             if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||||
|  |                 headers { | ||||||
|  |                     append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword())) | ||||||
|  |                     ) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|         }) |         }) | ||||||
|  |  | ||||||
|     suspend fun sourcesStats(): StatusAndData<ArrayList<SelfossModel.SourceStats>> = |     suspend fun sourcesStats(): StatusAndData<ArrayList<SelfossModel.SourceStats>> = | ||||||
| @@ -189,6 +278,12 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | |||||||
|                 parameter("username", appSettingsService.getUserName()) |                 parameter("username", appSettingsService.getUserName()) | ||||||
|                 parameter("password", appSettingsService.getPassword()) |                 parameter("password", appSettingsService.getPassword()) | ||||||
|             } |             } | ||||||
|  |             if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||||
|  |                 headers { | ||||||
|  |                     append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword())) | ||||||
|  |                     ) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|         }) |         }) | ||||||
|  |  | ||||||
|     suspend fun sourcesDetailed(): StatusAndData<ArrayList<SelfossModel.SourceDetail>> = |     suspend fun sourcesDetailed(): StatusAndData<ArrayList<SelfossModel.SourceDetail>> = | ||||||
| @@ -197,10 +292,23 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | |||||||
|                 parameter("username", appSettingsService.getUserName()) |                 parameter("username", appSettingsService.getUserName()) | ||||||
|                 parameter("password", appSettingsService.getPassword()) |                 parameter("password", appSettingsService.getPassword()) | ||||||
|             } |             } | ||||||
|  |             if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||||
|  |                 headers { | ||||||
|  |                     append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword())) | ||||||
|  |                     ) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|         }) |         }) | ||||||
|  |  | ||||||
|     suspend fun apiInformation(): StatusAndData<SelfossModel.ApiInformation> = |     suspend fun apiInformation(): StatusAndData<SelfossModel.ApiInformation> = | ||||||
|         bodyOrFailure(client.tryToGet(url("/api/about"))) |         bodyOrFailure(client.tryToGet(url("/api/about")) { | ||||||
|  |             if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||||
|  |                 headers { | ||||||
|  |                     append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword())) | ||||||
|  |                     ) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }) | ||||||
|  |  | ||||||
|     suspend fun markAsRead(id: String): SuccessResponse = |     suspend fun markAsRead(id: String): SuccessResponse = | ||||||
|         maybeResponse(client.tryToPost(url("/mark/$id")) { |         maybeResponse(client.tryToPost(url("/mark/$id")) { | ||||||
| @@ -208,6 +316,12 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | |||||||
|                 parameter("username", appSettingsService.getUserName()) |                 parameter("username", appSettingsService.getUserName()) | ||||||
|                 parameter("password", appSettingsService.getPassword()) |                 parameter("password", appSettingsService.getPassword()) | ||||||
|             } |             } | ||||||
|  |             if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||||
|  |                 headers { | ||||||
|  |                     append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword())) | ||||||
|  |                     ) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|         }) |         }) | ||||||
|  |  | ||||||
|     suspend fun unmarkAsRead(id: String): SuccessResponse = |     suspend fun unmarkAsRead(id: String): SuccessResponse = | ||||||
| @@ -216,6 +330,12 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | |||||||
|                 parameter("username", appSettingsService.getUserName()) |                 parameter("username", appSettingsService.getUserName()) | ||||||
|                 parameter("password", appSettingsService.getPassword()) |                 parameter("password", appSettingsService.getPassword()) | ||||||
|             } |             } | ||||||
|  |             if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||||
|  |                 headers { | ||||||
|  |                     append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword())) | ||||||
|  |                     ) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|         }) |         }) | ||||||
|  |  | ||||||
|     suspend fun starr(id: String): SuccessResponse = |     suspend fun starr(id: String): SuccessResponse = | ||||||
| @@ -224,6 +344,12 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | |||||||
|                 parameter("username", appSettingsService.getUserName()) |                 parameter("username", appSettingsService.getUserName()) | ||||||
|                 parameter("password", appSettingsService.getPassword()) |                 parameter("password", appSettingsService.getPassword()) | ||||||
|             } |             } | ||||||
|  |             if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||||
|  |                 headers { | ||||||
|  |                     append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword())) | ||||||
|  |                     ) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|         }) |         }) | ||||||
|  |  | ||||||
|     suspend fun unstarr(id: String): SuccessResponse = |     suspend fun unstarr(id: String): SuccessResponse = | ||||||
| @@ -232,6 +358,12 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | |||||||
|                 parameter("username", appSettingsService.getUserName()) |                 parameter("username", appSettingsService.getUserName()) | ||||||
|                 parameter("password", appSettingsService.getPassword()) |                 parameter("password", appSettingsService.getPassword()) | ||||||
|             } |             } | ||||||
|  |             if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||||
|  |                 headers { | ||||||
|  |                     append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword())) | ||||||
|  |                     ) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|         }) |         }) | ||||||
|  |  | ||||||
|     suspend fun markAllAsRead(ids: List<String>): SuccessResponse = |     suspend fun markAllAsRead(ids: List<String>): SuccessResponse = | ||||||
| @@ -243,6 +375,14 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | |||||||
|                     append("password", appSettingsService.getPassword()) |                     append("password", appSettingsService.getPassword()) | ||||||
|                 } |                 } | ||||||
|                 ids.map { append("ids[]", it) } |                 ids.map { append("ids[]", it) } | ||||||
|  |             }, | ||||||
|  |             block = { | ||||||
|  |                 if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||||
|  |                     headers { | ||||||
|  |                         append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword())) | ||||||
|  |                         ) | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         )) |         )) | ||||||
|  |  | ||||||
| @@ -278,6 +418,14 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | |||||||
|                 append("url", url) |                 append("url", url) | ||||||
|                 append("spout", spout) |                 append("spout", spout) | ||||||
|                 append(tagsParamName, tags) |                 append(tagsParamName, tags) | ||||||
|  |             }, | ||||||
|  |             block = { | ||||||
|  |                 if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||||
|  |                     headers { | ||||||
|  |                         append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword())) | ||||||
|  |                         ) | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
| @@ -315,6 +463,14 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | |||||||
|                 append("url", url) |                 append("url", url) | ||||||
|                 append("spout", spout) |                 append("spout", spout) | ||||||
|                 append(tagsParamName, tags) |                 append(tagsParamName, tags) | ||||||
|  |             }, | ||||||
|  |             block = { | ||||||
|  |                 if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||||
|  |                     headers { | ||||||
|  |                         append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword())) | ||||||
|  |                         ) | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
| @@ -324,5 +480,18 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | |||||||
|                 parameter("username", appSettingsService.getUserName()) |                 parameter("username", appSettingsService.getUserName()) | ||||||
|                 parameter("password", appSettingsService.getPassword()) |                 parameter("password", appSettingsService.getPassword()) | ||||||
|             } |             } | ||||||
|  |             if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||||
|  |                 headers { | ||||||
|  |                     append( | ||||||
|  |                         HttpHeaders.Authorization, | ||||||
|  |                         constructBasicAuthValue( | ||||||
|  |                             BasicAuthCredentials( | ||||||
|  |                                 username = appSettingsService.getBasicUserName(), | ||||||
|  |                                 password = appSettingsService.getBasicPassword() | ||||||
|  |                             ) | ||||||
|  |                         ) | ||||||
|  |                     ) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|         }) |         }) | ||||||
| } | } | ||||||
| @@ -8,9 +8,12 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) { | |||||||
|     // Api related |     // Api related | ||||||
|     private var _apiVersion: Int = -1 |     private var _apiVersion: Int = -1 | ||||||
|     private var _publicAccess: Boolean? = null |     private var _publicAccess: Boolean? = null | ||||||
|  |     private var _selfSigned: Boolean? = null | ||||||
|     private var _baseUrl: String = "" |     private var _baseUrl: String = "" | ||||||
|     private var _userName: String = "" |     private var _userName: String = "" | ||||||
|  |     private var _basicUserName: String = "" | ||||||
|     private var _password: String = "" |     private var _password: String = "" | ||||||
|  |     private var _basicPassword: String = "" | ||||||
|  |  | ||||||
|     // User settings related |     // User settings related | ||||||
|     private var _itemsCaching: Boolean? = null |     private var _itemsCaching: Boolean? = null | ||||||
| @@ -75,6 +78,22 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) { | |||||||
|         _publicAccess = settings.getBoolean(API_PUBLIC_ACCESS, 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 { |     fun getBaseUrl(): String { | ||||||
|         if (_baseUrl.isEmpty()) { |         if (_baseUrl.isEmpty()) { | ||||||
|             refreshBaseUrl() |             refreshBaseUrl() | ||||||
| @@ -96,6 +115,20 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) { | |||||||
|         return _password |         return _password | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     fun getBasicUserName(): String { | ||||||
|  |         if (_basicUserName.isEmpty()) { | ||||||
|  |             refreshBasicUsername() | ||||||
|  |         } | ||||||
|  |         return _basicUserName | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun getBasicPassword(): String { | ||||||
|  |         if (_basicPassword.isEmpty()) { | ||||||
|  |             refreshBasicPassword() | ||||||
|  |         } | ||||||
|  |         return _basicPassword | ||||||
|  |     } | ||||||
|  |  | ||||||
|     fun getItemsNumber(): Int { |     fun getItemsNumber(): Int { | ||||||
|         if (_itemsNumber == null) { |         if (_itemsNumber == null) { | ||||||
|             refreshItemsNumber() |             refreshItemsNumber() | ||||||
| @@ -149,6 +182,14 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) { | |||||||
|         _password = settings.getString(PASSWORD, "") |         _password = settings.getString(PASSWORD, "") | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private fun refreshBasicUsername() { | ||||||
|  |         _basicUserName = settings.getString(BASIC_LOGIN, "") | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun refreshBasicPassword() { | ||||||
|  |         _basicPassword = settings.getString(BASIC_PASSWORD, "") | ||||||
|  |     } | ||||||
|  |  | ||||||
|     private fun refreshArticleViewerEnabled() { |     private fun refreshArticleViewerEnabled() { | ||||||
|         _articleViewer = settings.getBoolean(PREFER_ARTICLE_VIEWER, true) |         _articleViewer = settings.getBoolean(PREFER_ARTICLE_VIEWER, true) | ||||||
|     } |     } | ||||||
| @@ -354,9 +395,12 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) { | |||||||
|     fun refreshApiSettings() { |     fun refreshApiSettings() { | ||||||
|         refreshPassword() |         refreshPassword() | ||||||
|         refreshUsername() |         refreshUsername() | ||||||
|  |         refreshBasicUsername() | ||||||
|  |         refreshBasicPassword() | ||||||
|         refreshBaseUrl() |         refreshBaseUrl() | ||||||
|         refreshApiVersion() |         refreshApiVersion() | ||||||
|         refreshPublicAccess() |         refreshPublicAccess() | ||||||
|  |         refreshSelfSigned() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun refreshUserSettings() { |     fun refreshUserSettings() { | ||||||
| @@ -387,7 +431,17 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) { | |||||||
|         login: String, |         login: String, | ||||||
|         password: String |         password: String | ||||||
|     ) { |     ) { | ||||||
|  |         val regex = """\/\/(\D+):(\D+)@""".toRegex() | ||||||
|  |         val matchResult = regex.find(url) | ||||||
|  |         if (matchResult != null) { | ||||||
|  |             val (basicLogin, basicPassword) = matchResult.destructured | ||||||
|  |             settings.putString(BASIC_LOGIN, basicLogin) | ||||||
|  |             settings.putString(BASIC_PASSWORD, basicPassword) | ||||||
|  |             val urlWithoutBasicAuth = url.replace(regex, "//") | ||||||
|  |             settings.putString(BASE_URL, urlWithoutBasicAuth) | ||||||
|  |         } else { | ||||||
|             settings.putString(BASE_URL, url) |             settings.putString(BASE_URL, url) | ||||||
|  |         } | ||||||
|         settings.putString(LOGIN, login) |         settings.putString(LOGIN, login) | ||||||
|         settings.putString(PASSWORD, password) |         settings.putString(PASSWORD, password) | ||||||
|         refreshApiSettings() |         refreshApiSettings() | ||||||
| @@ -397,6 +451,8 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) { | |||||||
|         settings.remove(BASE_URL) |         settings.remove(BASE_URL) | ||||||
|         settings.remove(LOGIN) |         settings.remove(LOGIN) | ||||||
|         settings.remove(PASSWORD) |         settings.remove(PASSWORD) | ||||||
|  |         settings.remove(BASIC_LOGIN) | ||||||
|  |         settings.remove(BASIC_PASSWORD) | ||||||
|         refreshApiSettings() |         refreshApiSettings() | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -430,6 +486,8 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) { | |||||||
|  |  | ||||||
|         const val API_PUBLIC_ACCESS = "apiPublicAccess" |         const val API_PUBLIC_ACCESS = "apiPublicAccess" | ||||||
|  |  | ||||||
|  |         const val API_SELF_SIGNED = "apiSelfSigned" | ||||||
|  |  | ||||||
|         const val API_ITEMS_NUMBER = "prefer_api_items_number" |         const val API_ITEMS_NUMBER = "prefer_api_items_number" | ||||||
|  |  | ||||||
|         const val API_TIMEOUT = "api_timeout" |         const val API_TIMEOUT = "api_timeout" | ||||||
| @@ -440,6 +498,10 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) { | |||||||
|  |  | ||||||
|         const val PASSWORD = "password" |         const val PASSWORD = "password" | ||||||
|  |  | ||||||
|  |         const val BASIC_LOGIN = "basic_login" | ||||||
|  |  | ||||||
|  |         const val BASIC_PASSWORD = "basic_password" | ||||||
|  |  | ||||||
|         const val PREFER_ARTICLE_VIEWER = "prefer_article_viewer" |         const val PREFER_ARTICLE_VIEWER = "prefer_article_viewer" | ||||||
|  |  | ||||||
|         const val CARD_VIEW_ACTIVE = "card_view_active" |         const val CARD_VIEW_ACTIVE = "card_view_active" | ||||||
| @@ -470,7 +532,6 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) { | |||||||
|  |  | ||||||
|         const val PERIODIC_REFRESH_MINUTES = "periodic_refresh_minutes" |         const val PERIODIC_REFRESH_MINUTES = "periodic_refresh_minutes" | ||||||
|  |  | ||||||
|  |  | ||||||
|         const val INFINITE_LOADING = "infinite_loading" |         const val INFINITE_LOADING = "infinite_loading" | ||||||
|  |  | ||||||
|         const val ITEMS_CACHING = "items_caching" |         const val ITEMS_CACHING = "items_caching" | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user