Compare commits

..

27 Commits

Author SHA1 Message Date
aminecmi
29d33687b4 fix: Fixing date formats.
Some checks are pending
continuous-integration/drone/pr Build is running
continuous-integration/drone/push Build is passing
2023-10-10 22:26:38 +02:00
aminecmi
33cca86383 chore: update versions 2023-10-10 21:41:34 +02:00
aminecmi
86a30f647d fix: Fixed ios build. 2023-10-10 21:32:28 +02:00
78d87ad52a Merge master
Some checks are pending
continuous-integration/drone/pr Build is running
2023-09-22 16:11:38 +02:00
Amine Louveau
c458871569 feat: Self signed ssl support.
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/141
2023-09-17 18:28:47 +00:00
056825aa0c Revert xmlns changes
All checks were successful
continuous-integration/drone/pr Build is passing
2023-09-17 12:01:32 +02:00
16b19fc5ce Revert version upgrades 2023-09-17 11:59:08 +02:00
2b82be61d0 Revert changes not related to the gradle upgrade
Some checks failed
continuous-integration/drone/pr Build is failing
2023-09-16 14:13:05 +02:00
4ad4a23ed8 Revert to private functions
Some checks failed
continuous-integration/drone/pr Build is failing
2023-09-12 00:38:37 +02:00
d8c215eacc Reintroduce removed parameter 2023-09-12 00:36:56 +02:00
2b446ab22b Revert dependency version changes 2023-09-12 00:36:04 +02:00
20aab0ea62 Improve image handling
Some checks failed
continuous-integration/drone/pr Build is failing
2023-09-12 00:28:36 +02:00
dcb9b1cb1f Enable non transitive R class 2023-09-12 00:28:36 +02:00
776d5d36f6 Remove deprecated function calls 2023-09-12 00:28:36 +02:00
990a354229 Remove deprecated function and prevent errors in case of a null fragment name 2023-09-12 00:28:36 +02:00
bd6b96d09d Remove not required jcenter repository 2023-09-12 00:28:36 +02:00
d26f3979cd Upgrade all dependencies 2023-09-12 00:28:36 +02:00
b59b1abc6d Fix R8 compilation problem 2023-09-12 00:25:14 +02:00
baca7cffed Upgrade to gradle 8 2023-09-12 00:24:59 +02:00
a029d8a7dc Move api client creation function within api class
All checks were successful
continuous-integration/drone/pr Build is passing
2023-09-10 21:33:28 +02:00
4482234e1a Remove unused strings file
All checks were successful
continuous-integration/drone/pr Build is passing
2023-09-10 21:15:04 +02:00
b5de30f561 Add translations 2023-09-10 21:14:43 +02:00
70ad5f322c Handle most HTTP client creation in common code 2023-09-10 21:14:43 +02:00
d167092c83 Add a login switch to disable SSL verification 2023-09-10 20:24:22 +02:00
c4f4bafe85 Add a switch in the login screen to disable SSL 2023-07-13 14:55:48 +02:00
ed06b22a77 Tentative self signed ssl support 2023-07-13 14:55:48 +02:00
aminecmi
172362b533 Changelog for v123061811 [CI SKIP] 2023-06-30 19:06:44 +00:00
38 changed files with 251 additions and 164 deletions

View File

@@ -3,35 +3,38 @@ type: docker
name: test name: test
steps: 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 - name: BuildAndTest
image: mingc/android-build-box:latest image: mingc/android-build-box:latest
commands: commands:
- echo "---------------------------------------------------------" - echo "---------------------------------------------------------"
- echo "Configure gradle..." - 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 "---------------------------------------------------------"
- echo "Building..." - echo "Configure java..."
- ./gradlew build -x test - . ~/.bash_profile
- jenv global 17.0
- java --version
- date
- echo "---------------------------------------------------------" - echo "---------------------------------------------------------"
- echo "Testing..." - echo "Building and testing..."
- ./gradlew build
- echo "---------------------------------------------------------" - 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: trigger:
event: event:
- push - push
@@ -107,10 +110,10 @@ steps:
- git fetch --tags - git fetch --tags
- echo "---------------------------------------------------------" - echo "---------------------------------------------------------"
- echo "Configure gradle..." - 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 "---------------------------------------------------------"
- echo "Generate APK" - echo "Generate APK"
- ./gradlew :androidApp:assembleGithubConfigRelease -P pushCache=false - ./gradlew :androidApp:assembleGithubConfigRelease
- echo "---------------------------------------------------------" - echo "---------------------------------------------------------"
- echo "Get Key" - echo "Get Key"
- wget https://amine-louveau.fr/key - wget https://amine-louveau.fr/key

View File

@@ -1,3 +1,10 @@
**v123061811**
- feat: Added confirmation dialog for disconnect item menu.
- Changelog for v123061651 [CI SKIP]
--------------------------------------------------------------------
**v123061651** **v123061651**
- i18n: Translation update. - i18n: Translation update.

View File

@@ -8,15 +8,15 @@ 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 {
var result: String = ByteArrayOutputStream().use { outputStream -> val result: String = ByteArrayOutputStream().use { outputStream ->
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()) { val 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 {
@@ -58,23 +57,22 @@ android {
compileOptions { compileOptions {
isCoreLibraryDesugaringEnabled = true isCoreLibraryDesugaringEnabled = true
// Flag to enable support for the new language APIs // Flag to enable support for the new language APIs
sourceCompatibility = JavaVersion.VERSION_11 sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_17
} }
// For Kotlin projects // For Kotlin projects
kotlinOptions { kotlinOptions {
jvmTarget = "11" jvmTarget = "17"
} }
compileSdk = 33 compileSdk = 34
buildToolsVersion = "33.0.0"
buildFeatures { buildFeatures {
viewBinding = true viewBinding = true
} }
defaultConfig { defaultConfig {
applicationId = "bou.amine.apps.readerforselfossv2.android" applicationId = "bou.amine.apps.readerforselfossv2.android"
minSdk = 21 minSdk = 25
targetSdk = 33 targetSdk = 34
versionCode = versionCodeFromGit() versionCode = versionCodeFromGit()
versionName = versionNameFromGit() versionName = versionNameFromGit()
@@ -87,7 +85,7 @@ android {
// tests // tests
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }
packagingOptions { packaging {
resources { resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}" excludes += "/META-INF/{AL2.0,LGPL2.1}"
} }
@@ -116,25 +114,25 @@ dependencies {
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.3") coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.3")
implementation(project(":shared")) implementation(project(":shared"))
implementation("com.google.android.material:material:1.5.0") implementation("com.google.android.material:material:1.9.0")
implementation("androidx.appcompat:appcompat:1.4.1") implementation("androidx.appcompat:appcompat:1.6.1")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1")
implementation("androidx.preference:preference-ktx:1.1.1") implementation("androidx.preference:preference-ktx:1.2.1")
implementation(fileTree(mapOf("include" to listOf("*.jar"), "dir" to "libs"))) implementation(fileTree(mapOf("include" to listOf("*.jar"), "dir" to "libs")))
// Android Support // Android Support
implementation("androidx.appcompat:appcompat:1.4.1") implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.5.0") implementation("com.google.android.material:material:1.9.0")
implementation("androidx.recyclerview:recyclerview:1.3.0-alpha01") implementation("androidx.recyclerview:recyclerview:1.3.1")
implementation("androidx.legacy:legacy-support-v4:1.0.0") implementation("androidx.legacy:legacy-support-v4:1.0.0")
implementation("androidx.vectordrawable:vectordrawable:1.2.0-alpha02") implementation("androidx.vectordrawable:vectordrawable:1.2.0-beta01")
implementation("androidx.cardview:cardview:1.0.0") implementation("androidx.cardview:cardview:1.0.0")
implementation("androidx.annotation:annotation:1.3.0") implementation("androidx.annotation:annotation:1.7.0")
implementation("androidx.work:work-runtime-ktx:2.7.1") implementation("androidx.work:work-runtime-ktx:2.8.1")
implementation("androidx.constraintlayout:constraintlayout:2.1.4") implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("org.jsoup:jsoup:1.14.3") implementation("org.jsoup:jsoup:1.15.4")
//multidex //multidex
implementation("androidx.multidex:multidex:2.0.1") implementation("androidx.multidex:multidex:2.0.1")
@@ -155,7 +153,7 @@ dependencies {
// Pager // Pager
implementation("me.relex:circleindicator:2.1.6") implementation("me.relex:circleindicator:2.1.6")
implementation("androidx.viewpager2:viewpager2:1.1.0-beta01") implementation("androidx.viewpager2:viewpager2:1.1.0-beta02")
//Dependency Injection //Dependency Injection
implementation("org.kodein.di:kodein-di:7.14.0") implementation("org.kodein.di:kodein-di:7.14.0")
@@ -171,7 +169,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.12.0")
implementation("androidx.lifecycle:lifecycle-extensions:2.2.0") implementation("androidx.lifecycle:lifecycle-extensions:2.2.0")
@@ -184,7 +182,7 @@ dependencies {
//test //test
testImplementation("junit:junit:4.13.2") testImplementation("junit:junit:4.13.2")
testImplementation("io.mockk:mockk:1.12.0") testImplementation("io.mockk:mockk:1.12.0")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0") testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0") implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
implementation("ch.acra:acra-http:$acraVersion") implementation("ch.acra:acra-http:$acraVersion")

View File

@@ -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.

View File

@@ -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()

View File

@@ -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,7 +144,7 @@ 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

View File

@@ -78,7 +78,7 @@ class SettingsActivity : AppCompatActivity(),
val args = pref.extras val args = pref.extras
val fragment = supportFragmentManager.fragmentFactory.instantiate( val fragment = supportFragmentManager.fragmentFactory.instantiate(
classLoader, classLoader,
pref.fragment pref.fragment.toString()
).apply { ).apply {
arguments = args arguments = args
setTargetFragment(caller, 0) setTargetFragment(caller, 0)

View File

@@ -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"

View File

@@ -130,4 +130,5 @@
<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_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string> <string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources> </resources>

View File

@@ -130,4 +130,5 @@
<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_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string> <string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources> </resources>

View File

@@ -130,4 +130,5 @@
<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_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string> <string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources> </resources>

View File

@@ -130,4 +130,5 @@
<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_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string> <string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources> </resources>

View File

@@ -130,4 +130,5 @@
<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_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string> <string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources> </resources>

View File

@@ -130,4 +130,5 @@
<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_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string> <string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources> </resources>

View File

@@ -130,4 +130,5 @@
<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_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string> <string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources> </resources>

View File

@@ -130,4 +130,5 @@
<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_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string> <string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources> </resources>

View File

@@ -130,4 +130,5 @@
<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_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string> <string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources> </resources>

View File

@@ -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>

View File

@@ -130,4 +130,5 @@
<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_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string> <string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources> </resources>

View File

@@ -130,4 +130,5 @@
<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_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string> <string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources> </resources>

View File

@@ -130,4 +130,5 @@
<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_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string> <string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources> </resources>

View File

@@ -130,4 +130,5 @@
<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_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string> <string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources> </resources>

View File

@@ -130,4 +130,5 @@
<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_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string> <string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources> </resources>

View File

@@ -130,4 +130,5 @@
<string name="update_source">更新源</string> <string name="update_source">更新源</string>
<string name="confirm_disconnect_title">Disconnect ?</string> <string name="confirm_disconnect_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string> <string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources> </resources>

View File

@@ -130,4 +130,5 @@
<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_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string> <string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources> </resources>

View File

@@ -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>

View File

@@ -9,38 +9,49 @@ import org.junit.Test
class DatesTest { class DatesTest {
private val v3Date = "2013-04-07T13:43:00+01:00" private val newVersionDateVariant = "2022-12-24T17:00:08+00"
private val v4Date = "2013-04-07 13:43:00" private val newVersionDate = "2013-04-07T13:43:00+01:00"
private val bug1Date = "2022-12-24T17:00:08+00" private val oldVersionDate = "2013-05-07 13:46:00"
private val oldVersionDateVariant = "2021-03-21 10:32:00.000000"
@Test @Test
fun v3_date_should_be_parsed() { fun new_version_date_should_be_parsed() {
val date = DateUtils.parseDate(v3Date) val date = DateUtils.parseDate(newVersionDate)
val expected =
LocalDateTime(2013, 4, 7, 14, 43, 0, 0).toInstant(TimeZone.currentSystemDefault())
.toEpochMilliseconds()
assertEquals(date, expected)
}
@Test
fun v4_date_should_be_parsed() {
val date = DateUtils.parseDate(v4Date)
val expected = val expected =
LocalDateTime(2013, 4, 7, 13, 43, 0, 0).toInstant(TimeZone.currentSystemDefault()) LocalDateTime(2013, 4, 7, 13, 43, 0, 0).toInstant(TimeZone.currentSystemDefault())
.toEpochMilliseconds() .toEpochMilliseconds()
assertEquals(date, expected) assertEquals(expected, date)
} }
@Test @Test
fun bug1_date_should_be_parsed() { fun old_version_date_should_be_parsed() {
val date = DateUtils.parseDate(bug1Date) val date = DateUtils.parseDate(oldVersionDate)
val expected = val expected =
LocalDateTime(2022, 12, 24, 18, 0, 8, 0).toInstant(TimeZone.currentSystemDefault()) LocalDateTime(2013, 5, 7, 13, 46, 0, 0).toInstant(TimeZone.currentSystemDefault())
.toEpochMilliseconds() .toEpochMilliseconds()
assertEquals(date, expected) assertEquals(expected, date)
}
@Test
fun old_version_variant_date_should_be_parsed() {
val date = DateUtils.parseDate(oldVersionDateVariant)
val expected =
LocalDateTime(2021, 3, 21, 10, 32, 0, 0).toInstant(TimeZone.currentSystemDefault())
.toEpochMilliseconds()
assertEquals(expected, date)
}
@Test
fun new_version_variant_date_should_be_parsed() {
val date = DateUtils.parseDate(newVersionDateVariant)
val expected =
LocalDateTime(2022, 12, 24, 17, 0, 8, 0).toInstant(TimeZone.currentSystemDefault())
.toEpochMilliseconds()
assertEquals(expected, date)
} }
} }

View File

@@ -1,18 +1,18 @@
buildscript { buildscript {
dependencies { dependencies {
// SqlDelight // SqlDelight
classpath("com.squareup.sqldelight:gradle-plugin:1.5.4") classpath("com.squareup.sqldelight:gradle-plugin:1.5.5")
} }
} }
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.2").apply(false)
id("com.android.library").version("7.4.0").apply(false) id("com.android.library").version("8.1.2").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 {
@@ -20,7 +20,6 @@ allprojects {
// maven { url = uri("https://nexus.amine-louveau.fr/repository/maven-public/")} // maven { url = uri("https://nexus.amine-louveau.fr/repository/maven-public/")}
google() google()
mavenCentral() mavenCentral()
jcenter()
maven { url = uri("https://www.jitpack.io") } maven { url = uri("https://www.jitpack.io") }
} }
} }

View File

@@ -13,24 +13,16 @@
#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
kotlin.native.cacheKind.iosX64=none kotlin.native.cacheKind.iosX64=none
pushCache=true

View File

@@ -1,6 +1,6 @@
#Mon Jan 23 20:47:46 CET 2023 #Thu Jul 13 11:41:19 CEST 2023
distributionBase=GRADLE_USER_HOME 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.1-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -1,5 +1,3 @@
val pushCache: String by settings
pluginManagement { pluginManagement {
repositories { repositories {
// maven { url = uri("https://nexus.amine-louveau.fr/repository/maven-public/")} // 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" rootProject.name = "ReaderForSelfossV2"
include(":androidApp") include(":androidApp")
include(":shared") include(":shared")

View File

@@ -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,12 @@ 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()
listOf( listOf(
iosX64(), iosX64(),
@@ -29,16 +31,18 @@ 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("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0") implementation("io.ktor:ktor-client-auth:$ktorVersion")
implementation("io.ktor:ktor-client-auth:2.1.1") implementation("io.ktor:ktor-client-cio:$ktorVersion")
implementation("org.jsoup:jsoup:1.14.3") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1")
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.14.0")
//Settings //Settings
implementation("com.russhwolf:multiplatform-settings-no-arg:1.0.0-RC") implementation("com.russhwolf:multiplatform-settings-no-arg:1.0.0-RC")
@@ -58,14 +62,15 @@ 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("io.ktor:ktor-client-okhttp:2.2.4")
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 = 34
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig { defaultConfig {
minSdk = 21 minSdk = 25
targetSdk = 32
} }
compileOptions { compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_17
} }
namespace = "bou.amine.apps.readerforselfossv2" namespace = "bou.amine.apps.readerforselfossv2"
} }

View File

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

View File

@@ -6,16 +6,25 @@ import kotlinx.datetime.*
actual class DateUtils { actual class DateUtils {
actual companion object { actual companion object {
// Possible formats are
// yyyy-mm-dd hh:mm:ss format
private val oldVersionFormat = "\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}(.()\\d*)?".toRegex()
// yyyy-MM-dd'T'HH:mm:ss[.SSS]XXX (RFC3339)
private val newVersionFormat = "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\+\\d{2}(:\\d{2})?".toRegex()
// We may need to consider moving the formatting to platform specific code, even if the tests are doubled
// For now, we handle this in a hacky way, because kotlin only accepts iso formats
actual fun parseDate(dateString: String): Long { actual fun parseDate(dateString: String): Long {
return try { var isoDateString: String = if (dateString.matches(oldVersionFormat)) {
Instant.parse(dateString).toEpochMilliseconds() dateString.replace(" ", "T")
} catch (e: Exception) { } else if (dateString.matches(newVersionFormat)) {
var str = dateString.replace(" ", "T") dateString.split("+")[0]
if (str.matches("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\+\\d{2}".toRegex())) { } else {
str = str.split("+")[0] throw Exception("Unrecognized format for $dateString")
}
LocalDateTime.parse(str).toInstant(TimeZone.currentSystemDefault()).toEpochMilliseconds()
} }
return LocalDateTime.parse(isoDateString).toInstant(TimeZone.currentSystemDefault()).toEpochMilliseconds()
} }
actual fun parseRelativeDate(dateString: String): String { actual fun parseRelativeDate(dateString: String): String {

View File

@@ -5,31 +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.auth.providers.* import io.ktor.client.engine.cio.CIOEngineConfig
import io.ktor.client.plugins.cache.* import io.ktor.client.plugins.HttpRequestRetry
import io.ktor.client.plugins.contentnegotiation.* import io.ktor.client.plugins.HttpTimeout
import io.ktor.client.plugins.cookies.* import io.ktor.client.plugins.auth.providers.BasicAuthCredentials
import io.ktor.client.plugins.logging.* import io.ktor.client.plugins.cache.HttpCache
import io.ktor.client.request.* import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.statement.* import io.ktor.client.plugins.cookies.HttpCookies
import io.ktor.http.* import io.ktor.client.plugins.logging.LogLevel
import io.ktor.serialization.kotlinx.json.* import io.ktor.client.plugins.logging.Logger
import io.ktor.util.* import io.ktor.client.plugins.logging.Logging
import io.ktor.utils.io.charsets.* import io.ktor.client.request.get
import io.ktor.utils.io.core.* 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
expect fun setupInsecureHTTPEngine(config: CIOEngineConfig)
class SelfossApi(private val appSettingsService: AppSettingsService) { class SelfossApi(private val appSettingsService: AppSettingsService) {
var client = createHttpClient() var client = createHttpClient()
fun createHttpClient() =
private fun createHttpClient(): HttpClient { HttpClient(CIO) {
val client = HttpClient { if (appSettingsService.getSelfSigned()) {
engine {
setupInsecureHTTPEngine(this)
}
}
install(ContentNegotiation) { install(ContentNegotiation) {
install(HttpCache) install(HttpCache)
json(Json { json(Json {
@@ -60,7 +75,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
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() login()
Napier.i("Did login", tag = "HttpSend") Napier.i("Did login", tag = "HttpSend")
} }
} }
@@ -68,10 +83,6 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
expectSuccess = false expectSuccess = false
} }
return client
}
fun url(path: String) = fun url(path: String) =
"${appSettingsService.getBaseUrl()}$path" "${appSettingsService.getBaseUrl()}$path"

View File

@@ -8,6 +8,7 @@ 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 _basicUserName: String = ""
@@ -77,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()
@@ -383,6 +400,7 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
refreshBaseUrl() refreshBaseUrl()
refreshApiVersion() refreshApiVersion()
refreshPublicAccess() refreshPublicAccess()
refreshSelfSigned()
} }
fun refreshUserSettings() { fun refreshUserSettings() {
@@ -468,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"

View File

@@ -0,0 +1,6 @@
package bou.amine.apps.readerforselfossv2.rest
import io.ktor.client.engine.cio.CIOEngineConfig
actual fun setupInsecureHTTPEngine(config: CIOEngineConfig) {
}

View File

@@ -0,0 +1,6 @@
package bou.amine.apps.readerforselfossv2.rest
import io.ktor.client.engine.cio.CIOEngineConfig
actual fun setupInsecureHTTPEngine(config: CIOEngineConfig) {
}