Compare commits
	
		
			35 Commits
		
	
	
		
			v123041021
			...
			f0f998368b
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | f0f998368b | ||
|  | c458871569 | ||
| 056825aa0c | |||
| 16b19fc5ce | |||
| 4ad4a23ed8 | |||
| d8c215eacc | |||
| 2b446ab22b | |||
| a029d8a7dc | |||
| 4482234e1a | |||
| b5de30f561 | |||
| 70ad5f322c | |||
| d167092c83 | |||
| c4f4bafe85 | |||
| ed06b22a77 | |||
|  | 172362b533 | ||
|  | ad72cb6f56 | ||
|  | 9057ee0052 | ||
|  | 50d0b44315 | ||
|  | 21b08ed384 | ||
|  | 993c4d2ee9 | ||
|  | 57a9d51027 | ||
|  | 673f0edb8b | ||
|  | 7f96798f13 | ||
|  | 6e5704a45b | ||
|  | 495591159f | ||
|  | 718fe7c5ee | ||
|  | ecd23213f9 | ||
|  | e6baed8cb4 | ||
|  | c87abec0b9 | ||
|  | 0aba41d8bf | ||
|  | 2a2d1047b4 | ||
|  | 66ef1ccf32 | ||
|  | 677ede5bc7 | ||
|  | 996a7ed22c | ||
|  | 85208c4e5a | 
							
								
								
									
										49
									
								
								.drone.yml
									
									
									
									
									
								
							
							
						
						
									
										49
									
								
								.drone.yml
									
									
									
									
									
								
							| @@ -3,35 +3,38 @@ type: docker | ||||
| name: test | ||||
|  | ||||
| steps: | ||||
|   - name: Lint | ||||
|     failure: ignore | ||||
|     image: mingc/android-build-box:latest | ||||
|     commands: | ||||
|       - echo "---------------------------------------------------------" | ||||
|       - echo "Install linters..." | ||||
|       - curl -sSLO https://github.com/pinterest/ktlint/releases/download/1.0.0/ktlint && chmod a+x ktlint && mv ktlint /usr/local/bin/ | ||||
|       - curl -sSLO https://github.com/detekt/detekt/releases/download/v1.23.1/detekt-cli-1.23.1.zip && unzip detekt-cli-1.23.1.zip | ||||
|       - echo "---------------------------------------------------------" | ||||
|       - echo "Linting..." | ||||
|       - ktlint || true | ||||
|       - echo "---------------------------------------------------------" | ||||
|       - echo "Detecting..." | ||||
|       - ./detekt-cli-1.23.1/bin/detekt-cli --all-rules || true | ||||
|       - echo "---------------------------------------------------------" | ||||
|     command_timeout: 1m | ||||
|   - name: BuildAndTest | ||||
|     image: mingc/android-build-box:latest | ||||
|     commands: | ||||
|       - echo "---------------------------------------------------------" | ||||
|       - echo "Configure gradle..." | ||||
|       - mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=true\npushCache=false\nsystemProp.org.gradle.internal.http.connectionTimeout=180000\nsystemProp.org.gradle.internal.http.socketTimeout=180000" >> ~/.gradle/gradle.properties | ||||
|       - mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=true\nsystemProp.org.gradle.internal.http.connectionTimeout=180000\nsystemProp.org.gradle.internal.http.socketTimeout=180000" >> ~/.gradle/gradle.properties | ||||
|       - echo "---------------------------------------------------------" | ||||
|       - echo "Building..." | ||||
|       - ./gradlew build -x test | ||||
|       - echo "Configure java..." | ||||
|       - . ~/.bash_profile | ||||
|       - jenv global 17.0 | ||||
|       - java --version | ||||
|       - date | ||||
|       - echo "---------------------------------------------------------" | ||||
|       - echo "Testing..." | ||||
|       - echo "Building and testing..." | ||||
|       - ./gradlew build | ||||
|       - echo "---------------------------------------------------------" | ||||
|       - ./gradlew koverMergedXmlReport | ||||
|     environment: | ||||
|       TZ: Europe/Paris | ||||
|       SONAR_HOST_URL: | ||||
|         from_secret: sonarScannerHostUrl | ||||
|       SONAR_LOGIN: | ||||
|         from_secret: sonarScannerLogin | ||||
|   - name: Analyse | ||||
|     image: kytay/sonar-node-plugin | ||||
|     settings: | ||||
|       sonar_host: | ||||
|         from_secret: sonarScannerHostUrl | ||||
|       sonar_token: | ||||
|         from_secret: sonarScannerLogin | ||||
|       use_node_version: 16.18.1 | ||||
|       sonar_debug: true | ||||
|       sonar_project_settings: ./sonar-project.properties | ||||
| trigger: | ||||
|   event: | ||||
|     - push | ||||
| @@ -107,10 +110,10 @@ steps: | ||||
|       - git fetch --tags | ||||
|       - echo "---------------------------------------------------------" | ||||
|       - echo "Configure gradle..." | ||||
|       - mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=false\npushCache=false\nsystemProp.org.gradle.internal.http.connectionTimeout=180000\nsystemProp.org.gradle.internal.http.socketTimeout=180000" >> ~/.gradle/gradle.properties | ||||
|       - mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=false\nsystemProp.org.gradle.internal.http.connectionTimeout=180000\nsystemProp.org.gradle.internal.http.socketTimeout=180000" >> ~/.gradle/gradle.properties | ||||
|       - echo "---------------------------------------------------------" | ||||
|       - echo "Generate APK" | ||||
|       - ./gradlew :androidApp:assembleGithubConfigRelease  -P pushCache=false | ||||
|       - ./gradlew :androidApp:assembleGithubConfigRelease | ||||
|       - echo "---------------------------------------------------------" | ||||
|       - echo "Get Key" | ||||
|       - wget https://amine-louveau.fr/key | ||||
|   | ||||
							
								
								
									
										63
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										63
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,3 +1,66 @@ | ||||
| **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** | ||||
|  | ||||
| - fix: 'Enable Core Library Desugaring to support older Android versions' (#138) from davidoskky/ReaderForSelfoss-multiplatform:desugaring into master | ||||
| - Enable Core Library Desugaring to support older Android versions | ||||
| - Changelog for v123030851 [CI SKIP] | ||||
|  | ||||
| -------------------------------------------------------------------- | ||||
|  | ||||
| **v123030851** | ||||
|  | ||||
| - chore: replace textDrawable library (#136) | ||||
|   | ||||
| @@ -599,12 +599,14 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar | ||||
|                 return true | ||||
|             } | ||||
|             R.id.action_disconnect -> { | ||||
|                 runBlocking { | ||||
|                     repository.logout() | ||||
|                 needsConfirmation(R.string.confirm_disconnect_title, R.string.confirm_disconnect_description) { | ||||
|                     runBlocking { | ||||
|                         repository.logout() | ||||
|                     } | ||||
|                     val intent = Intent(this, LoginActivity::class.java) | ||||
|                     this.startActivity(intent) | ||||
|                     finish() | ||||
|                 } | ||||
|                 val intent = Intent(this, LoginActivity::class.java) | ||||
|                 this.startActivity(intent) | ||||
|                 finish() | ||||
|                 return true | ||||
|             } | ||||
|             R.id.action_settings -> { | ||||
|   | ||||
| @@ -139,9 +139,12 @@ class LoginActivity : AppCompatActivity(), DIAware { | ||||
|  | ||||
|         showProgress(true) | ||||
|  | ||||
|         appSettingsService.updateSelfSigned(binding.selfSigned.isChecked) | ||||
|  | ||||
|         repository.refreshLoginInformation(url, login, password) | ||||
|  | ||||
|         CoroutineScope(Dispatchers.Main).launch { | ||||
|             repository.updateApiInformation() | ||||
|             val result = repository.login() | ||||
|             if (result) { | ||||
|                 val (errorFetching, displaySelfossOnly) = repository.shouldBeSelfossInstance() | ||||
|   | ||||
| @@ -71,7 +71,11 @@ class ReaderActivity : AppCompatActivity(), DIAware { | ||||
|             finish() | ||||
|         } | ||||
|  | ||||
|         readItem(allItems[currentItem]) | ||||
|         try { | ||||
|             readItem(allItems[currentItem]) | ||||
|         } catch (e: IndexOutOfBoundsException) { | ||||
|             finish() | ||||
|         } | ||||
|  | ||||
|         binding.pager.adapter = ScreenSlidePagerAdapter(this) | ||||
|         binding.pager.setCurrentItem(currentItem, false) | ||||
|   | ||||
| @@ -27,11 +27,11 @@ import bou.amine.apps.readerforselfossv2.android.model.toModel | ||||
| import bou.amine.apps.readerforselfossv2.android.model.toParcelable | ||||
| import bou.amine.apps.readerforselfossv2.android.sendSilentlyWithAcraWithName | ||||
| import bou.amine.apps.readerforselfossv2.android.utils.glide.getBitmapInputStream | ||||
| import bou.amine.apps.readerforselfossv2.android.utils.isUrlValid | ||||
| import bou.amine.apps.readerforselfossv2.android.utils.openInBrowserAsNewTask | ||||
| import bou.amine.apps.readerforselfossv2.android.utils.shareLink | ||||
| import bou.amine.apps.readerforselfossv2.model.MercuryModel | ||||
| import bou.amine.apps.readerforselfossv2.model.SelfossModel | ||||
| import bou.amine.apps.readerforselfossv2.model.StatusAndData | ||||
| import bou.amine.apps.readerforselfossv2.repository.Repository | ||||
| import bou.amine.apps.readerforselfossv2.rest.MercuryApi | ||||
| import bou.amine.apps.readerforselfossv2.service.AppSettingsService | ||||
| @@ -260,21 +260,8 @@ class ArticleFragment : Fragment(), DIAware { | ||||
|         CoroutineScope(Dispatchers.Main).launch { | ||||
|             try { | ||||
|                 val response = mercuryApi.query(url) | ||||
|                 if (response.success && response.data != null && !response.data?.content.isNullOrEmpty()) { | ||||
|                     binding.titleView.text = response.data!!.title.orEmpty() | ||||
|                     if (typeface != null) { | ||||
|                         binding.titleView.typeface = typeface | ||||
|                     } | ||||
|                     URL(response.data!!.url) | ||||
|                     url = response.data!!.url | ||||
|  | ||||
|                     contentText = response.data!!.content.orEmpty() | ||||
|                     htmlToWebview() | ||||
|  | ||||
|                     handleLeadImage(response) | ||||
|  | ||||
|                     binding.nestedScrollView.scrollTo(0, 0) | ||||
|                     binding.progressBar.visibility = View.GONE | ||||
|                 if (response.success && response.data != null) { | ||||
|                     handleMercuryData(response.data!!) | ||||
|                 } else { | ||||
|                     openInBrowserAfterFailing() | ||||
|                 } | ||||
| @@ -287,14 +274,35 @@ class ArticleFragment : Fragment(), DIAware { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun handleLeadImage(response: StatusAndData<MercuryModel.ParsedContent>) { | ||||
|         if (!response.data?.lead_image_url.isNullOrEmpty() && context != null) { | ||||
|     private fun handleMercuryData(data: MercuryModel.ParsedContent) { | ||||
|         if (data.error == true || data.failed == true) { | ||||
|             openInBrowserAfterFailing() | ||||
|         } else { | ||||
|             binding.titleView.text = data.title.orEmpty() | ||||
|             if (typeface != null) { | ||||
|                 binding.titleView.typeface = typeface | ||||
|             } | ||||
|             URL(data.url) | ||||
|             url = data.url!! | ||||
|  | ||||
|             contentText = data.content.orEmpty() | ||||
|             htmlToWebview() | ||||
|  | ||||
|             handleLeadImage(data?.lead_image_url) | ||||
|  | ||||
|             binding.nestedScrollView.scrollTo(0, 0) | ||||
|             binding.progressBar.visibility = View.GONE | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun handleLeadImage(lead_image_url: String?) { | ||||
|         if (!lead_image_url.isNullOrEmpty() && context != null) { | ||||
|             binding.imageView.visibility = View.VISIBLE | ||||
|             Glide | ||||
|                 .with(requireContext()) | ||||
|                 .asBitmap() | ||||
|                 .load( | ||||
|                     response.data!!.lead_image_url.orEmpty() | ||||
|                     lead_image_url | ||||
|                 ) | ||||
|                 .apply(RequestOptions.fitCenterTransform()) | ||||
|                 .into(binding.imageView) | ||||
| @@ -307,7 +315,7 @@ class ArticleFragment : Fragment(), DIAware { | ||||
|         binding.webcontent.webViewClient = object : WebViewClient() { | ||||
|             @Deprecated("Deprecated in Java") | ||||
|             override fun shouldOverrideUrlLoading(view: WebView?, url: String): Boolean { | ||||
|                 return if (context != null && binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) { | ||||
|                 return if (context != null && url.isUrlValid() && binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) { | ||||
|                     try { | ||||
|                         requireContext().startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) | ||||
|                     } catch (e: ActivityNotFoundException) { | ||||
| @@ -523,7 +531,12 @@ class ArticleFragment : Fragment(), DIAware { | ||||
|  | ||||
|     private fun openInBrowserAfterFailing() { | ||||
|         binding.progressBar.visibility = View.GONE | ||||
|         requireActivity().openInBrowserAsNewTask(this@ArticleFragment.item) | ||||
|         if (context != null) { | ||||
|             requireContext().openInBrowserAsNewTask(this@ArticleFragment.item) | ||||
|         } else { | ||||
|             Exception("openInBrowserAfterFailing context is null").sendSilentlyWithAcraWithName("openInBrowserAfterFailing > $context") | ||||
|  | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     companion object { | ||||
| @@ -541,8 +554,8 @@ class ArticleFragment : Fragment(), DIAware { | ||||
|     } | ||||
|  | ||||
|     fun performClick(): Boolean { | ||||
|         if (binding.webcontent.hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE || | ||||
|             binding.webcontent.hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE | ||||
|         if (allImages != null && (binding.webcontent.hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE || | ||||
|             binding.webcontent.hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) | ||||
|         ) { | ||||
|  | ||||
|             val position: Int = allImages.indexOf(binding.webcontent.hitTestResult.extra) | ||||
|   | ||||
| @@ -149,21 +149,23 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware { | ||||
|             c.ellipsize = TextUtils.TruncateAt.END | ||||
|             c.text = tag.tag | ||||
|  | ||||
|             try { | ||||
|                 val gd = GradientDrawable() | ||||
|                 val gdColor = try { | ||||
|                     Color.parseColor(tag.color) | ||||
|                 } catch (e: IllegalArgumentException) { | ||||
|                     e.sendSilentlyWithAcraWithName("color issue " + tag.color) | ||||
|                     resources.getColor(R.color.colorPrimary) | ||||
|             if (tag.color.isNotEmpty()) { | ||||
|                 try { | ||||
|                     val gd = GradientDrawable() | ||||
|                     val gdColor = try { | ||||
|                         Color.parseColor(tag.color) | ||||
|                     } catch (e: IllegalArgumentException) { | ||||
|                         e.sendSilentlyWithAcraWithName("color issue " + tag.color) | ||||
|                         resources.getColor(R.color.colorPrimary) | ||||
|                     } | ||||
|                     gd.setColor(gdColor) | ||||
|                     gd.shape = GradientDrawable.RECTANGLE | ||||
|                     gd.setSize(30, 30) | ||||
|                     gd.cornerRadius = 30F | ||||
|                     c.chipIcon = gd | ||||
|                 } catch (e: Exception) { | ||||
|                     e.sendSilentlyWithAcraWithName("tags > GradientDrawable") | ||||
|                 } | ||||
|                 gd.setColor(gdColor) | ||||
|                 gd.shape = GradientDrawable.RECTANGLE | ||||
|                 gd.setSize(30, 30) | ||||
|                 gd.cornerRadius = 30F | ||||
|                 c.chipIcon = gd | ||||
|             } catch (e: Exception) { | ||||
|                 e.sendSilentlyWithAcraWithName("tags > GradientDrawable") | ||||
|             } | ||||
|  | ||||
|             c.setOnCloseIconClickListener { | ||||
|   | ||||
| @@ -51,6 +51,13 @@ | ||||
|                 android:maxLines="1" | ||||
|                 android:minHeight="48dp" /> | ||||
|  | ||||
|             <com.google.android.material.switchmaterial.SwitchMaterial | ||||
|                 android:id="@+id/selfSigned" | ||||
|                 android:layout_width="match_parent" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:text="@string/disable_ssl" | ||||
|                 android:textAlignment="viewStart" /> | ||||
|  | ||||
|             <com.google.android.material.switchmaterial.SwitchMaterial | ||||
|                 android:id="@+id/withLogin" | ||||
|                 android:layout_width="match_parent" | ||||
|   | ||||
| @@ -128,4 +128,7 @@ | ||||
|     <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> | ||||
|     <string name="menu_home_sources">Sources</string> | ||||
|     <string name="update_source">Update source</string> | ||||
|     <string name="confirm_disconnect_title">Disconnect ?</string> | ||||
|     <string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string> | ||||
|     <string name="disable_ssl">Disable SSL</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -128,4 +128,7 @@ | ||||
|     <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> | ||||
|     <string name="menu_home_sources">Sources</string> | ||||
|     <string name="update_source">Update source</string> | ||||
|     <string name="confirm_disconnect_title">Disconnect ?</string> | ||||
|     <string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string> | ||||
|     <string name="disable_ssl">Disable SSL</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -128,4 +128,7 @@ | ||||
|     <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> | ||||
|     <string name="menu_home_sources">Sources</string> | ||||
|     <string name="update_source">Update source</string> | ||||
|     <string name="confirm_disconnect_title">Disconnect ?</string> | ||||
|     <string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string> | ||||
|     <string name="disable_ssl">Disable SSL</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -128,4 +128,7 @@ | ||||
|     <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> | ||||
|     <string name="menu_home_sources">Sources</string> | ||||
|     <string name="update_source">Update source</string> | ||||
|     <string name="confirm_disconnect_title">Disconnect ?</string> | ||||
|     <string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string> | ||||
|     <string name="disable_ssl">Disable SSL</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -128,4 +128,7 @@ | ||||
|     <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> | ||||
|     <string name="menu_home_sources">Sources</string> | ||||
|     <string name="update_source">Update source</string> | ||||
|     <string name="confirm_disconnect_title">Disconnect ?</string> | ||||
|     <string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string> | ||||
|     <string name="disable_ssl">Disable SSL</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -33,8 +33,8 @@ | ||||
|     <string name="addStringNoUrl">"Accede pra engadir fontes."</string> | ||||
|     <string name="cant_get_sources">"Non se pode obter a lista de fontes."</string> | ||||
|     <string name="cant_create_source">"Non se pode crear unha fonte."</string> | ||||
|     <string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string> | ||||
|     <string name="cant_get_spouts">"Can't get spouts list. There may ben an api issue."</string> | ||||
|     <string name="cant_get_spouts_no_network">"Non se pode obter a lista de spouts por mor dun erro de rede."</string> | ||||
|     <string name="cant_get_spouts">"Non se pode obter a lista de spoits. Pode que haxa algún problema coa api."</string> | ||||
|     <string name="form_not_complete">"O formulario non está completo"</string> | ||||
|     <string name="pref_header_links">"Ligazóns"</string> | ||||
|     <string name="issue_tracker_link">"Rastrexador de Incidencias"</string> | ||||
| @@ -116,16 +116,19 @@ | ||||
|     <string name="reader_static_bar_on">A barra inferior mostrarase sempre</string> | ||||
|     <string name="reader_static_bar_off">A barra inferior pode mostrarse a través do botón flotante</string> | ||||
|     <string name="remove_source">Eliminar fonte</string> | ||||
|     <string name="pref_theme_title">Light/Dark mode</string> | ||||
|     <string name="mode_dark">Dark mode</string> | ||||
|     <string name="mode_system">Follow the system setting</string> | ||||
|     <string name="mode_light">Light mode</string> | ||||
|     <string name="gdpr_dialog_title">The app does not share any personal data about you.</string> | ||||
|     <string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string> | ||||
|     <string name="crash_toast_text">A crash occured. Sending the details to the developper.</string> | ||||
|     <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> | ||||
|     <string name="menu_home_filter">Filters</string> | ||||
|     <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> | ||||
|     <string name="pref_theme_title">Modo Claro/Escuro</string> | ||||
|     <string name="mode_dark">Modo escuro</string> | ||||
|     <string name="mode_system">Seguir axustes do sistema</string> | ||||
|     <string name="mode_light">Modo claro</string> | ||||
|     <string name="gdpr_dialog_title">A aplicación non comparte ningún dato persoal seu.</string> | ||||
|     <string name="gdpr_dialog_message"><![CDATA[O envío de informes de erros está habilitado. Pode deshabilitarse dende a páxina de axustes. Ten en conta que os informes de erros son esenciais para o desenvolvemento da aplicación.]]></string> | ||||
|     <string name="crash_toast_text">Ocurriu un erro. Enviando os detalles o desenvolvedor.</string> | ||||
|     <string name="pref_switch_disable_acra">"Deshabilitar o reporte automático de erros. "</string> | ||||
|     <string name="menu_home_filter">Filtros</string> | ||||
|     <string name="application_selfoss_only">Esta aplicación só funciona cunha instancia de Selfoss, e con ningún outro filtro RSS.</string> | ||||
|     <string name="menu_home_sources">Sources</string> | ||||
|     <string name="update_source">Update source</string> | ||||
|     <string name="confirm_disconnect_title">Disconnect ?</string> | ||||
|     <string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string> | ||||
|     <string name="disable_ssl">Disable SSL</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -90,7 +90,7 @@ | ||||
|     <string name="pref_switch_items_caching">Save items for offline use</string> | ||||
|     <string name="pref_switch_update_sources">Check for new sources and tags</string> | ||||
|     <string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string> | ||||
|     <string name="network_connectivity_lost">"Network connection lost"</string> | ||||
|     <string name="network_connectivity_lost">"Koneksi jaringan hilang"</string> | ||||
|     <string name="network_connectivity_retrieved">"Network connection is now available"</string> | ||||
|     <string name="pref_switch_periodic_refresh">Sync articles</string> | ||||
|     <string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string> | ||||
| @@ -128,4 +128,7 @@ | ||||
|     <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> | ||||
|     <string name="menu_home_sources">Sources</string> | ||||
|     <string name="update_source">Update source</string> | ||||
|     <string name="confirm_disconnect_title">Disconnect ?</string> | ||||
|     <string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string> | ||||
|     <string name="disable_ssl">Disable SSL</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -128,4 +128,7 @@ | ||||
|     <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> | ||||
|     <string name="menu_home_sources">Sources</string> | ||||
|     <string name="update_source">Update source</string> | ||||
|     <string name="confirm_disconnect_title">Disconnect ?</string> | ||||
|     <string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string> | ||||
|     <string name="disable_ssl">Disable SSL</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -128,4 +128,7 @@ | ||||
|     <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> | ||||
|     <string name="menu_home_sources">Sources</string> | ||||
|     <string name="update_source">Update source</string> | ||||
|     <string name="confirm_disconnect_title">Disconnect ?</string> | ||||
|     <string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string> | ||||
|     <string name="disable_ssl">Disable SSL</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -1,11 +0,0 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|     <string name="gdpr_dialog_title">The app does not share any personal data about you.</string> | ||||
|     <string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string> | ||||
|     <string name="crash_toast_text">A crash occured. Sending the details to the developper.</string> | ||||
|     <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> | ||||
|     <string name="menu_home_filter">Filters</string> | ||||
|     <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> | ||||
|     <string name="menu_home_sources">Sources</string> | ||||
|     <string name="update_source">Update source</string> | ||||
| </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="menu_home_sources">Sources</string> | ||||
|     <string name="update_source">Update source</string> | ||||
|     <string name="confirm_disconnect_title">Disconnect ?</string> | ||||
|     <string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string> | ||||
|     <string name="disable_ssl">Disable SSL</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -128,4 +128,7 @@ | ||||
|     <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> | ||||
|     <string name="menu_home_sources">Sources</string> | ||||
|     <string name="update_source">Update source</string> | ||||
|     <string name="confirm_disconnect_title">Disconnect ?</string> | ||||
|     <string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string> | ||||
|     <string name="disable_ssl">Disable SSL</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -128,4 +128,7 @@ | ||||
|     <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> | ||||
|     <string name="menu_home_sources">Sources</string> | ||||
|     <string name="update_source">Update source</string> | ||||
|     <string name="confirm_disconnect_title">Disconnect ?</string> | ||||
|     <string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string> | ||||
|     <string name="disable_ssl">Disable SSL</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -128,4 +128,7 @@ | ||||
|     <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> | ||||
|     <string name="menu_home_sources">Sources</string> | ||||
|     <string name="update_source">Update source</string> | ||||
|     <string name="confirm_disconnect_title">Disconnect ?</string> | ||||
|     <string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string> | ||||
|     <string name="disable_ssl">Disable SSL</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -128,4 +128,7 @@ | ||||
|     <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> | ||||
|     <string name="menu_home_sources">Sources</string> | ||||
|     <string name="update_source">Update source</string> | ||||
|     <string name="confirm_disconnect_title">Disconnect ?</string> | ||||
|     <string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string> | ||||
|     <string name="disable_ssl">Disable SSL</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -125,7 +125,10 @@ | ||||
|     <string name="crash_toast_text">发生崩溃。请将细节发送给开发人员。</string> | ||||
|     <string name="pref_switch_disable_acra">"禁用自动错误报告 "</string> | ||||
|     <string name="menu_home_filter">筛选器</string> | ||||
|     <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> | ||||
|     <string name="menu_home_sources">Sources</string> | ||||
|     <string name="update_source">Update source</string> | ||||
|     <string name="application_selfoss_only">此应用只适用于 Selfoss 实例,不适用于其他 RSS 。</string> | ||||
|     <string name="menu_home_sources">源</string> | ||||
|     <string name="update_source">更新源</string> | ||||
|     <string name="confirm_disconnect_title">Disconnect ?</string> | ||||
|     <string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string> | ||||
|     <string name="disable_ssl">Disable SSL</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -128,4 +128,7 @@ | ||||
|     <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> | ||||
|     <string name="menu_home_sources">Sources</string> | ||||
|     <string name="update_source">Update source</string> | ||||
|     <string name="confirm_disconnect_title">Disconnect ?</string> | ||||
|     <string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string> | ||||
|     <string name="disable_ssl">Disable SSL</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -6,6 +6,7 @@ | ||||
|     <string name="error_invalid_password">"Password not long enough"</string> | ||||
|     <string name="error_field_required">"Field required"</string> | ||||
|     <string name="prompt_url">"Url"</string> | ||||
|     <string name="disable_ssl">"Disable SSL"</string> | ||||
|     <string name="withLoginSwitch">"Login required ?"</string> | ||||
|     <string name="login_url_problem">"Oops. You may need to add a \"/\" at the end of the url."</string> | ||||
|     <string name="prompt_login">"Username"</string> | ||||
| @@ -131,4 +132,6 @@ | ||||
|     <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> | ||||
|     <string name="menu_home_sources">Sources</string> | ||||
|     <string name="update_source">Update source</string> | ||||
|     <string name="confirm_disconnect_title">Disconnect ?</string> | ||||
|     <string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -33,4 +33,3 @@ org.gradle.parallel=true | ||||
| org.gradle.caching=true | ||||
| ignoreGitVersion=false | ||||
| kotlin.native.cacheKind.iosX64=none | ||||
| pushCache=true | ||||
|   | ||||
| @@ -1,5 +1,3 @@ | ||||
| val pushCache: String by settings | ||||
|  | ||||
| pluginManagement { | ||||
|     repositories { | ||||
|         // maven { url = uri("https://nexus.amine-louveau.fr/repository/maven-public/")} | ||||
| @@ -17,17 +15,6 @@ dependencyResolutionManagement { | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| buildCache { | ||||
|     remote<HttpBuildCache> { | ||||
|         url = uri("http://18.0.0.7:3071/cache/") | ||||
|         isAllowInsecureProtocol = true | ||||
|         isAllowUntrustedServer = true | ||||
|         isUseExpectContinue = true | ||||
|         isPush = (pushCache == "true") | ||||
|     } | ||||
| } | ||||
|  | ||||
| rootProject.name = "ReaderForSelfossV2" | ||||
| include(":androidApp") | ||||
| include(":shared") | ||||
| @@ -35,6 +35,7 @@ kotlin { | ||||
|                 implementation("io.ktor:ktor-client-logging:2.1.1") | ||||
|                 implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0") | ||||
|                 implementation("io.ktor:ktor-client-auth:2.1.1") | ||||
|                 implementation("io.ktor:ktor-client-cio:2.1.1") | ||||
|                 implementation("org.jsoup:jsoup:1.14.3") | ||||
|  | ||||
|                 //Dependency Injection | ||||
|   | ||||
| @@ -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,9 +6,12 @@ class MercuryModel { | ||||
|  | ||||
|     @Serializable | ||||
|     class ParsedContent( | ||||
|         val title: String?, | ||||
|         val content: String?, | ||||
|         val lead_image_url: String?, // NOSONAR | ||||
|         val url: String | ||||
|         val title: String? = null, | ||||
|         val content: String? = null, | ||||
|         val lead_image_url: String? = null, // NOSONAR | ||||
|         val url: String? = null, | ||||
|         val error: Boolean? = null, | ||||
|         val message: String? = null, | ||||
|         val failed: Boolean? = null | ||||
|     ) | ||||
| } | ||||
|   | ||||
| @@ -24,8 +24,8 @@ class SelfossModel { | ||||
|     @Serializable | ||||
|     class Stats( | ||||
|         val total: Int, | ||||
|         val unread: Int?, | ||||
|         val starred: Int? | ||||
|         val unread: Int? = null, | ||||
|         val starred: Int? = null | ||||
|     ) | ||||
|  | ||||
|     @Serializable | ||||
| @@ -36,9 +36,9 @@ class SelfossModel { | ||||
|  | ||||
|     @Serializable | ||||
|     data class ApiInformation( | ||||
|         val version: String?, | ||||
|         val apiversion: String?, | ||||
|         val configuration: ApiConfiguration? | ||||
|         val version: String? = null, | ||||
|         val apiversion: String? = null, | ||||
|         val configuration: ApiConfiguration? = null | ||||
|     ) { | ||||
|         fun getApiMajorVersion(): Int { | ||||
|             var versionNumber = 0 | ||||
| @@ -54,9 +54,9 @@ class SelfossModel { | ||||
|     @Serializable | ||||
|     data class ApiConfiguration( | ||||
|         @Serializable(with = BooleanSerializer::class) | ||||
|         val publicMode: Boolean?, | ||||
|         val publicMode: Boolean? = null, | ||||
|         @Serializable(with = BooleanSerializer::class) | ||||
|         val authEnabled: Boolean? | ||||
|         val authEnabled: Boolean? = null | ||||
|     ) { | ||||
|         fun isAuthEnabled() = authEnabled ?: true | ||||
|  | ||||
| @@ -75,7 +75,7 @@ class SelfossModel { | ||||
|     data class SourceStats( | ||||
|         override val id: Int, | ||||
|         override var title: String, | ||||
|         override var unread: Int?, | ||||
|         override var unread: Int? = null, | ||||
|         override var error: String? = null, | ||||
|         override var icon: String? = null | ||||
|         ) : Source | ||||
| @@ -86,11 +86,11 @@ class SelfossModel { | ||||
|         override var title: String, | ||||
|         override var unread: Int? = null, | ||||
|         @Serializable(with = TagsListSerializer::class) | ||||
|         var tags: List<String>?, | ||||
|         var spout: String?, | ||||
|         override var error: String?, | ||||
|         override var icon: String?, | ||||
|         var params: SourceParams? | ||||
|         var tags: List<String>? = null, | ||||
|         var spout: String? = null, | ||||
|         override var error: String? = null, | ||||
|         override var icon: String? = null, | ||||
|         var params: SourceParams? = null | ||||
|     ) : Source | ||||
|  | ||||
|     @Serializable | ||||
| @@ -107,13 +107,13 @@ class SelfossModel { | ||||
|         var unread: Boolean, | ||||
|         @Serializable(with = BooleanSerializer::class) | ||||
|         var starred: Boolean, | ||||
|         val thumbnail: String?, | ||||
|         val icon: String?, | ||||
|         val thumbnail: String? = null, | ||||
|         val icon: String? = null, | ||||
|         val link: String, | ||||
|         val sourcetitle: String, | ||||
|         @Serializable(with = TagsListSerializer::class) | ||||
|         val tags: List<String>, | ||||
|         val author: String? | ||||
|         val author: String? = null | ||||
|     ) { | ||||
|         fun getLinkDecoded(): String { | ||||
|             var stringUrl: String | ||||
|   | ||||
| @@ -5,33 +5,53 @@ import bou.amine.apps.readerforselfossv2.model.StatusAndData | ||||
| import bou.amine.apps.readerforselfossv2.model.SuccessResponse | ||||
| import bou.amine.apps.readerforselfossv2.service.AppSettingsService | ||||
| import io.github.aakira.napier.Napier | ||||
| import io.ktor.client.* | ||||
| import io.ktor.client.plugins.* | ||||
| import io.ktor.client.plugins.cache.* | ||||
| import io.ktor.client.plugins.contentnegotiation.* | ||||
| import io.ktor.client.plugins.cookies.* | ||||
| import io.ktor.client.plugins.logging.* | ||||
| import io.ktor.client.request.* | ||||
| import io.ktor.client.statement.* | ||||
| import io.ktor.http.* | ||||
| import io.ktor.serialization.kotlinx.json.* | ||||
| import io.ktor.client.HttpClient | ||||
| import io.ktor.client.engine.cio.CIO | ||||
| import io.ktor.client.engine.cio.CIOEngineConfig | ||||
| import io.ktor.client.plugins.HttpRequestRetry | ||||
| import io.ktor.client.plugins.HttpTimeout | ||||
| import io.ktor.client.plugins.auth.providers.BasicAuthCredentials | ||||
| import io.ktor.client.plugins.cache.HttpCache | ||||
| import io.ktor.client.plugins.contentnegotiation.ContentNegotiation | ||||
| import io.ktor.client.plugins.cookies.HttpCookies | ||||
| import io.ktor.client.plugins.logging.LogLevel | ||||
| import io.ktor.client.plugins.logging.Logger | ||||
| import io.ktor.client.plugins.logging.Logging | ||||
| import io.ktor.client.request.get | ||||
| import io.ktor.client.request.headers | ||||
| import io.ktor.client.request.parameter | ||||
| import io.ktor.client.statement.HttpResponse | ||||
| import io.ktor.http.HttpHeaders | ||||
| import io.ktor.http.HttpStatusCode | ||||
| import io.ktor.http.Parameters | ||||
| import io.ktor.serialization.kotlinx.json.json | ||||
| import io.ktor.util.encodeBase64 | ||||
| import io.ktor.utils.io.charsets.Charsets | ||||
| import io.ktor.utils.io.core.toByteArray | ||||
| import kotlinx.coroutines.CoroutineScope | ||||
| import kotlinx.coroutines.Dispatchers | ||||
| import kotlinx.coroutines.launch | ||||
| import kotlinx.serialization.json.Json | ||||
|  | ||||
| expect fun setupInsecureHTTPEngine(config: CIOEngineConfig) | ||||
|  | ||||
| class SelfossApi(private val appSettingsService: AppSettingsService) { | ||||
|  | ||||
|     var client = createHttpClient() | ||||
|  | ||||
|     private fun createHttpClient(): HttpClient { | ||||
|         val client = HttpClient { | ||||
|     fun createHttpClient() = | ||||
|         HttpClient(CIO) { | ||||
|             if (appSettingsService.getSelfSigned()) { | ||||
|                 engine { | ||||
|                     setupInsecureHTTPEngine(this) | ||||
|                 } | ||||
|             } | ||||
|             install(ContentNegotiation) { | ||||
|                 install(HttpCache) | ||||
|                 json(Json { | ||||
|                     prettyPrint = true | ||||
|                     isLenient = true | ||||
|                     ignoreUnknownKeys = true | ||||
|                     explicitNulls = false | ||||
|                 }) | ||||
|             } | ||||
|             install(Logging) { | ||||
| @@ -55,7 +75,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | ||||
|                     Napier.i("Will modify", tag = "HttpSend") | ||||
|                     CoroutineScope(Dispatchers.Main).launch { | ||||
|                         Napier.i("Will login", tag = "HttpSend") | ||||
|                         this@SelfossApi.login() | ||||
|                         login() | ||||
|                         Napier.i("Did login", tag = "HttpSend") | ||||
|                     } | ||||
|                 } | ||||
| @@ -63,9 +83,6 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | ||||
|             expectSuccess = false | ||||
|         } | ||||
|  | ||||
|         return client | ||||
|     } | ||||
|  | ||||
|     fun url(path: String) = | ||||
|         "${appSettingsService.getBaseUrl()}$path" | ||||
|  | ||||
| @@ -74,6 +91,13 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | ||||
|         client = createHttpClient() | ||||
|     } | ||||
|  | ||||
|     fun constructBasicAuthValue(credentials: BasicAuthCredentials): String { | ||||
|         val authString = "${credentials.username}:${credentials.password}" | ||||
|         val authBuf = authString.toByteArray(Charsets.UTF_8).encodeBase64() | ||||
|  | ||||
|         return "Basic $authBuf" | ||||
|     } | ||||
|  | ||||
|     // Api version was introduces after the POST login, so when there is a version, it should be available | ||||
|     private fun shouldHavePostLogin() = appSettingsService.getApiVersion() != -1 | ||||
|     private fun hasLoginInfo() = | ||||
| @@ -96,11 +120,23 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | ||||
|     private suspend fun getLogin() = maybeResponse(client.tryToGet(url("/login")) { | ||||
|         parameter("username", appSettingsService.getUserName()) | ||||
|         parameter("password", appSettingsService.getPassword()) | ||||
|         if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||
|             headers { | ||||
|                 append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword())) | ||||
|                 ) | ||||
|             } | ||||
|         } | ||||
|     }) | ||||
|  | ||||
|     private suspend fun postLogin() = maybeResponse(client.tryToPost(url("/login")) { | ||||
|         parameter("username", appSettingsService.getUserName()) | ||||
|         parameter("password", appSettingsService.getPassword()) | ||||
|         if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||
|             headers { | ||||
|                 append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword())) | ||||
|                 ) | ||||
|             } | ||||
|         } | ||||
|     }) | ||||
|  | ||||
|     private fun shouldHaveNewLogout() = | ||||
| @@ -114,9 +150,23 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | ||||
|         } | ||||
|  | ||||
|     private suspend fun maybeLogoutIfAvailable() = | ||||
|         responseOrSuccessIf404(client.tryToGet(url("/logout"))) | ||||
|         responseOrSuccessIf404(client.tryToGet(url("/logout")) { | ||||
|             if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||
|                 headers { | ||||
|                     append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword())) | ||||
|                     ) | ||||
|                 } | ||||
|             } | ||||
|         }) | ||||
|  | ||||
|     private suspend fun doLogout() = maybeResponse(client.tryToDelete(url("/api/session/current"))) | ||||
|     private suspend fun doLogout() = maybeResponse(client.tryToDelete(url("/api/session/current")) { | ||||
|         if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||
|             headers { | ||||
|                 append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword())) | ||||
|                 ) | ||||
|             } | ||||
|         } | ||||
|     }) | ||||
|  | ||||
|     suspend fun getItems( | ||||
|         type: String, | ||||
| @@ -139,6 +189,12 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | ||||
|             parameter("updatedsince", updatedSince) | ||||
|             parameter("items", items ?: appSettingsService.getItemsNumber()) | ||||
|             parameter("offset", offset) | ||||
|             if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||
|                 headers { | ||||
|                     append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword())) | ||||
|                     ) | ||||
|                 } | ||||
|             } | ||||
|         }) | ||||
|  | ||||
|     suspend fun getItemsWithoutCatch(): StatusAndData<List<SelfossModel.Item>> = | ||||
| @@ -149,6 +205,12 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | ||||
|             } | ||||
|             parameter("type", "all") | ||||
|             parameter("items", 1) | ||||
|             if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||
|                 headers { | ||||
|                     append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword())) | ||||
|                     ) | ||||
|                 } | ||||
|             } | ||||
|         }) | ||||
|  | ||||
|     suspend fun stats(): StatusAndData<SelfossModel.Stats> = | ||||
| @@ -157,6 +219,12 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | ||||
|                 parameter("username", appSettingsService.getUserName()) | ||||
|                 parameter("password", appSettingsService.getPassword()) | ||||
|             } | ||||
|             if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||
|                 headers { | ||||
|                     append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword())) | ||||
|                     ) | ||||
|                 } | ||||
|             } | ||||
|         }) | ||||
|  | ||||
|     suspend fun tags(): StatusAndData<List<SelfossModel.Tag>> = | ||||
| @@ -165,6 +233,12 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | ||||
|                 parameter("username", appSettingsService.getUserName()) | ||||
|                 parameter("password", appSettingsService.getPassword()) | ||||
|             } | ||||
|             if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||
|                 headers { | ||||
|                     append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword())) | ||||
|                     ) | ||||
|                 } | ||||
|             } | ||||
|         }) | ||||
|  | ||||
|     suspend fun update(): StatusAndData<String> = | ||||
| @@ -173,6 +247,12 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | ||||
|                 parameter("username", appSettingsService.getUserName()) | ||||
|                 parameter("password", appSettingsService.getPassword()) | ||||
|             } | ||||
|             if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||
|                 headers { | ||||
|                     append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword())) | ||||
|                     ) | ||||
|                 } | ||||
|             } | ||||
|         }) | ||||
|  | ||||
|     suspend fun spouts(): StatusAndData<Map<String, SelfossModel.Spout>> = | ||||
| @@ -181,6 +261,12 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | ||||
|                 parameter("username", appSettingsService.getUserName()) | ||||
|                 parameter("password", appSettingsService.getPassword()) | ||||
|             } | ||||
|             if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||
|                 headers { | ||||
|                     append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword())) | ||||
|                     ) | ||||
|                 } | ||||
|             } | ||||
|         }) | ||||
|  | ||||
|     suspend fun sourcesStats(): StatusAndData<ArrayList<SelfossModel.SourceStats>> = | ||||
| @@ -189,6 +275,12 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | ||||
|                 parameter("username", appSettingsService.getUserName()) | ||||
|                 parameter("password", appSettingsService.getPassword()) | ||||
|             } | ||||
|             if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||
|                 headers { | ||||
|                     append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword())) | ||||
|                     ) | ||||
|                 } | ||||
|             } | ||||
|         }) | ||||
|  | ||||
|     suspend fun sourcesDetailed(): StatusAndData<ArrayList<SelfossModel.SourceDetail>> = | ||||
| @@ -197,10 +289,23 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | ||||
|                 parameter("username", appSettingsService.getUserName()) | ||||
|                 parameter("password", appSettingsService.getPassword()) | ||||
|             } | ||||
|             if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||
|                 headers { | ||||
|                     append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword())) | ||||
|                     ) | ||||
|                 } | ||||
|             } | ||||
|         }) | ||||
|  | ||||
|     suspend fun apiInformation(): StatusAndData<SelfossModel.ApiInformation> = | ||||
|         bodyOrFailure(client.tryToGet(url("/api/about"))) | ||||
|         bodyOrFailure(client.tryToGet(url("/api/about")) { | ||||
|             if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||
|                 headers { | ||||
|                     append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword())) | ||||
|                     ) | ||||
|                 } | ||||
|             } | ||||
|         }) | ||||
|  | ||||
|     suspend fun markAsRead(id: String): SuccessResponse = | ||||
|         maybeResponse(client.tryToPost(url("/mark/$id")) { | ||||
| @@ -208,6 +313,12 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | ||||
|                 parameter("username", appSettingsService.getUserName()) | ||||
|                 parameter("password", appSettingsService.getPassword()) | ||||
|             } | ||||
|             if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||
|                 headers { | ||||
|                     append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword())) | ||||
|                     ) | ||||
|                 } | ||||
|             } | ||||
|         }) | ||||
|  | ||||
|     suspend fun unmarkAsRead(id: String): SuccessResponse = | ||||
| @@ -216,6 +327,12 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | ||||
|                 parameter("username", appSettingsService.getUserName()) | ||||
|                 parameter("password", appSettingsService.getPassword()) | ||||
|             } | ||||
|             if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||
|                 headers { | ||||
|                     append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword())) | ||||
|                     ) | ||||
|                 } | ||||
|             } | ||||
|         }) | ||||
|  | ||||
|     suspend fun starr(id: String): SuccessResponse = | ||||
| @@ -224,6 +341,12 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | ||||
|                 parameter("username", appSettingsService.getUserName()) | ||||
|                 parameter("password", appSettingsService.getPassword()) | ||||
|             } | ||||
|             if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||
|                 headers { | ||||
|                     append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword())) | ||||
|                     ) | ||||
|                 } | ||||
|             } | ||||
|         }) | ||||
|  | ||||
|     suspend fun unstarr(id: String): SuccessResponse = | ||||
| @@ -232,6 +355,12 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | ||||
|                 parameter("username", appSettingsService.getUserName()) | ||||
|                 parameter("password", appSettingsService.getPassword()) | ||||
|             } | ||||
|             if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||
|                 headers { | ||||
|                     append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword())) | ||||
|                     ) | ||||
|                 } | ||||
|             } | ||||
|         }) | ||||
|  | ||||
|     suspend fun markAllAsRead(ids: List<String>): SuccessResponse = | ||||
| @@ -243,6 +372,14 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | ||||
|                     append("password", appSettingsService.getPassword()) | ||||
|                 } | ||||
|                 ids.map { append("ids[]", it) } | ||||
|             }, | ||||
|             block = { | ||||
|                 if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||
|                     headers { | ||||
|                         append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword())) | ||||
|                         ) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         )) | ||||
|  | ||||
| @@ -278,6 +415,14 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | ||||
|                 append("url", url) | ||||
|                 append("spout", spout) | ||||
|                 append(tagsParamName, tags) | ||||
|             }, | ||||
|             block = { | ||||
|                 if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||
|                     headers { | ||||
|                         append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword())) | ||||
|                         ) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         ) | ||||
|  | ||||
| @@ -315,6 +460,14 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | ||||
|                 append("url", url) | ||||
|                 append("spout", spout) | ||||
|                 append(tagsParamName, tags) | ||||
|             }, | ||||
|             block = { | ||||
|                 if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||
|                     headers { | ||||
|                         append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword())) | ||||
|                         ) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         ) | ||||
|  | ||||
| @@ -324,5 +477,18 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | ||||
|                 parameter("username", appSettingsService.getUserName()) | ||||
|                 parameter("password", appSettingsService.getPassword()) | ||||
|             } | ||||
|             if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { | ||||
|                 headers { | ||||
|                     append( | ||||
|                         HttpHeaders.Authorization, | ||||
|                         constructBasicAuthValue( | ||||
|                             BasicAuthCredentials( | ||||
|                                 username = appSettingsService.getBasicUserName(), | ||||
|                                 password = appSettingsService.getBasicPassword() | ||||
|                             ) | ||||
|                         ) | ||||
|                     ) | ||||
|                 } | ||||
|             } | ||||
|         }) | ||||
| } | ||||
| @@ -8,9 +8,12 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) { | ||||
|     // Api related | ||||
|     private var _apiVersion: Int = -1 | ||||
|     private var _publicAccess: Boolean? = null | ||||
|     private var _selfSigned: Boolean? = null | ||||
|     private var _baseUrl: String = "" | ||||
|     private var _userName: String = "" | ||||
|     private var _basicUserName: String = "" | ||||
|     private var _password: String = "" | ||||
|     private var _basicPassword: String = "" | ||||
|  | ||||
|     // User settings related | ||||
|     private var _itemsCaching: Boolean? = null | ||||
| @@ -75,6 +78,22 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) { | ||||
|         _publicAccess = settings.getBoolean(API_PUBLIC_ACCESS, false) | ||||
|     } | ||||
|  | ||||
|     fun getSelfSigned(): Boolean { | ||||
|         if (_selfSigned == null) { | ||||
|             refreshSelfSigned() | ||||
|         } | ||||
|         return _selfSigned!! | ||||
|     } | ||||
|  | ||||
|     fun updateSelfSigned(selfSigned: Boolean) { | ||||
|         settings.putBoolean(API_SELF_SIGNED, selfSigned) | ||||
|         refreshSelfSigned() | ||||
|     } | ||||
|  | ||||
|     private fun refreshSelfSigned() { | ||||
|         _selfSigned = settings.getBoolean(API_SELF_SIGNED, false) | ||||
|     } | ||||
|  | ||||
|     fun getBaseUrl(): String { | ||||
|         if (_baseUrl.isEmpty()) { | ||||
|             refreshBaseUrl() | ||||
| @@ -96,6 +115,20 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) { | ||||
|         return _password | ||||
|     } | ||||
|  | ||||
|     fun getBasicUserName(): String { | ||||
|         if (_basicUserName.isEmpty()) { | ||||
|             refreshBasicUsername() | ||||
|         } | ||||
|         return _basicUserName | ||||
|     } | ||||
|  | ||||
|     fun getBasicPassword(): String { | ||||
|         if (_basicPassword.isEmpty()) { | ||||
|             refreshBasicPassword() | ||||
|         } | ||||
|         return _basicPassword | ||||
|     } | ||||
|  | ||||
|     fun getItemsNumber(): Int { | ||||
|         if (_itemsNumber == null) { | ||||
|             refreshItemsNumber() | ||||
| @@ -149,6 +182,14 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) { | ||||
|         _password = settings.getString(PASSWORD, "") | ||||
|     } | ||||
|  | ||||
|     private fun refreshBasicUsername() { | ||||
|         _basicUserName = settings.getString(BASIC_LOGIN, "") | ||||
|     } | ||||
|  | ||||
|     private fun refreshBasicPassword() { | ||||
|         _basicPassword = settings.getString(BASIC_PASSWORD, "") | ||||
|     } | ||||
|  | ||||
|     private fun refreshArticleViewerEnabled() { | ||||
|         _articleViewer = settings.getBoolean(PREFER_ARTICLE_VIEWER, true) | ||||
|     } | ||||
| @@ -354,9 +395,12 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) { | ||||
|     fun refreshApiSettings() { | ||||
|         refreshPassword() | ||||
|         refreshUsername() | ||||
|         refreshBasicUsername() | ||||
|         refreshBasicPassword() | ||||
|         refreshBaseUrl() | ||||
|         refreshApiVersion() | ||||
|         refreshPublicAccess() | ||||
|         refreshSelfSigned() | ||||
|     } | ||||
|  | ||||
|     fun refreshUserSettings() { | ||||
| @@ -387,7 +431,17 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) { | ||||
|         login: String, | ||||
|         password: String | ||||
|     ) { | ||||
|         settings.putString(BASE_URL, url) | ||||
|         val regex = """\/\/(\D+):(\D+)@""".toRegex() | ||||
|         val matchResult = regex.find(url) | ||||
|         if (matchResult != null) { | ||||
|             val (basicLogin, basicPassword) = matchResult.destructured | ||||
|             settings.putString(BASIC_LOGIN, basicLogin) | ||||
|             settings.putString(BASIC_PASSWORD, basicPassword) | ||||
|             val urlWithoutBasicAuth = url.replace(regex, "//") | ||||
|             settings.putString(BASE_URL, urlWithoutBasicAuth) | ||||
|         } else { | ||||
|             settings.putString(BASE_URL, url) | ||||
|         } | ||||
|         settings.putString(LOGIN, login) | ||||
|         settings.putString(PASSWORD, password) | ||||
|         refreshApiSettings() | ||||
| @@ -397,6 +451,8 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) { | ||||
|         settings.remove(BASE_URL) | ||||
|         settings.remove(LOGIN) | ||||
|         settings.remove(PASSWORD) | ||||
|         settings.remove(BASIC_LOGIN) | ||||
|         settings.remove(BASIC_PASSWORD) | ||||
|         refreshApiSettings() | ||||
|     } | ||||
|  | ||||
| @@ -430,6 +486,8 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) { | ||||
|  | ||||
|         const val API_PUBLIC_ACCESS = "apiPublicAccess" | ||||
|  | ||||
|         const val API_SELF_SIGNED = "apiSelfSigned" | ||||
|  | ||||
|         const val API_ITEMS_NUMBER = "prefer_api_items_number" | ||||
|  | ||||
|         const val API_TIMEOUT = "api_timeout" | ||||
| @@ -440,6 +498,10 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) { | ||||
|  | ||||
|         const val PASSWORD = "password" | ||||
|  | ||||
|         const val BASIC_LOGIN = "basic_login" | ||||
|  | ||||
|         const val BASIC_PASSWORD = "basic_password" | ||||
|  | ||||
|         const val PREFER_ARTICLE_VIEWER = "prefer_article_viewer" | ||||
|  | ||||
|         const val CARD_VIEW_ACTIVE = "card_view_active" | ||||
| @@ -470,7 +532,6 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) { | ||||
|  | ||||
|         const val PERIODIC_REFRESH_MINUTES = "periodic_refresh_minutes" | ||||
|  | ||||
|  | ||||
|         const val INFINITE_LOADING = "infinite_loading" | ||||
|  | ||||
|         const val ITEMS_CACHING = "items_caching" | ||||
|   | ||||
| @@ -0,0 +1,6 @@ | ||||
| package bou.amine.apps.readerforselfossv2.rest | ||||
|  | ||||
| import io.ktor.client.engine.cio.CIOEngineConfig | ||||
|  | ||||
| actual fun setupInsecureHTTPEngine(config: CIOEngineConfig) { | ||||
| } | ||||
| @@ -0,0 +1,6 @@ | ||||
| package bou.amine.apps.readerforselfossv2.rest | ||||
|  | ||||
| import io.ktor.client.engine.cio.CIOEngineConfig | ||||
|  | ||||
| actual fun setupInsecureHTTPEngine(config: CIOEngineConfig) { | ||||
| } | ||||
		Reference in New Issue
	
	Block a user