Compare commits

..

No commits in common. "11c39ae87cf5f5ed9deb8cd016786cb029aaabec" and "5bc2f614af0c63ec3e32b3fa3684e0108d702b8a" have entirely different histories.

21 changed files with 184 additions and 258 deletions

View File

@ -24,5 +24,4 @@ jobs:
- name: Detecting... - name: Detecting...
run: ./detekt-cli-1.23.1/bin/detekt-cli --all-rules --excludes '**/shared/build/**/*.kt' || true run: ./detekt-cli-1.23.1/bin/detekt-cli --all-rules --excludes '**/shared/build/**/*.kt' || true
build: build:
needs: Lint
uses: ./.gitea/workflows/common_build.yml uses: ./.gitea/workflows/common_build.yml

View File

@ -1,7 +1,7 @@
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
val ignoreGitVersion: String by project val ignoreGitVersion: String by project
val acraVersion = "5.12.0" val acraVersion = "5.9.7"
plugins { plugins {
id("com.android.application") id("com.android.application")
@ -9,7 +9,6 @@ plugins {
kotlin("kapt") kotlin("kapt")
id("com.mikepenz.aboutlibraries.plugin") id("com.mikepenz.aboutlibraries.plugin")
id("org.jetbrains.kotlinx.kover") id("org.jetbrains.kotlinx.kover")
id("app.cash.sqldelight") version "2.0.2"
} }
fun Project.execWithOutput(cmd: String, ignore: Boolean = false): String { fun Project.execWithOutput(cmd: String, ignore: Boolean = false): String {
@ -66,14 +65,14 @@ android {
kotlinOptions { kotlinOptions {
jvmTarget = "17" jvmTarget = "17"
} }
compileSdk = 35 compileSdk = 34
buildFeatures { buildFeatures {
viewBinding = true viewBinding = true
} }
defaultConfig { defaultConfig {
applicationId = "bou.amine.apps.readerforselfossv2.android" applicationId = "bou.amine.apps.readerforselfossv2.android"
minSdk = 25 minSdk = 25
targetSdk = 34 // 35 when edge-to-edge is handled targetSdk = 34
versionCode = versionCodeFromGit() versionCode = versionCodeFromGit()
versionName = versionNameFromGit() versionName = versionNameFromGit()
@ -120,26 +119,28 @@ android {
} }
dependencies { dependencies {
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4") coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.3")
implementation(project(":shared")) implementation(project(":shared"))
implementation("androidx.appcompat:appcompat:1.7.0") implementation("com.google.android.material:material:1.9.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.1") implementation("androidx.appcompat:appcompat:1.6.1")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1")
implementation("androidx.preference:preference-ktx:1.2.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("com.google.android.material:material:1.12.0") implementation("androidx.appcompat:appcompat:1.6.1")
implementation("androidx.recyclerview:recyclerview:1.4.0-rc01") implementation("com.google.android.material:material:1.9.0")
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") 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.9.1") implementation("androidx.annotation:annotation:1.7.0")
implementation("androidx.work:work-runtime-ktx:2.10.0") implementation("androidx.work:work-runtime-ktx:2.8.1")
implementation("androidx.constraintlayout:constraintlayout:2.2.0") implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("org.jsoup:jsoup:1.18.3") implementation("org.jsoup:jsoup:1.15.4")
//multidex //multidex
implementation("androidx.multidex:multidex:2.0.1") implementation("androidx.multidex:multidex:2.0.1")
@ -152,31 +153,31 @@ dependencies {
implementation("com.ashokvarma.android:bottom-navigation-bar:2.2.0") implementation("com.ashokvarma.android:bottom-navigation-bar:2.2.0")
// glide // glide
kapt("com.github.bumptech.glide:compiler:4.16.0") kapt("com.github.bumptech.glide:compiler:4.15.0")
implementation("com.github.bumptech.glide:okhttp3-integration:4.16.0") implementation("com.github.bumptech.glide:okhttp3-integration:4.15.0")
// Themes // Themes
implementation("com.github.rubensousa:floatingtoolbar:1.5.1") implementation("com.github.rubensousa:floatingtoolbar:1.5.1")
// Pager // Pager
implementation("me.relex:circleindicator:2.1.6") implementation("me.relex:circleindicator:2.1.6")
implementation("androidx.viewpager2:viewpager2:1.1.0") implementation("androidx.viewpager2:viewpager2:1.1.0-beta02")
//Dependency Injection //Dependency Injection
implementation("org.kodein.di:kodein-di:7.23.1") implementation("org.kodein.di:kodein-di:7.14.0")
implementation("org.kodein.di:kodein-di-framework-android-x:7.23.1") implementation("org.kodein.di:kodein-di-framework-android-x:7.14.0")
implementation("org.kodein.di:kodein-di-framework-android-x-viewmodel:7.23.1") implementation("org.kodein.di:kodein-di-framework-android-x-viewmodel:7.14.0")
//Settings //Settings
implementation("com.russhwolf:multiplatform-settings-no-arg:1.3.0") implementation("com.russhwolf:multiplatform-settings-no-arg:0.9")
//Logging //Logging
implementation("io.github.aakira:napier:2.7.1") implementation("io.github.aakira:napier:2.6.1")
//PhotoView //PhotoView
implementation("com.github.chrisbanes:PhotoView:2.3.0") implementation("com.github.chrisbanes:PhotoView:2.3.0")
implementation("androidx.core:core-ktx:1.15.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,13 +185,13 @@ dependencies {
implementation("com.github.ln-12:multiplatform-connectivity-status:1.3.0") implementation("com.github.ln-12:multiplatform-connectivity-status:1.3.0")
// SQLDELIGHT // SQLDELIGHT
implementation("app.cash.sqldelight:android-driver:2.0.2") implementation("com.squareup.sqldelight:android-driver:1.5.4")
//test //test
testImplementation("junit:junit:4.13.2") testImplementation("junit:junit:4.13.2")
testImplementation("io.mockk:mockk:1.13.14") testImplementation("io.mockk:mockk:1.12.0")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.1") testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.1") implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
androidTestImplementation("androidx.test:runner:1.6.2") androidTestImplementation("androidx.test:runner:1.6.2")
androidTestImplementation("androidx.test:rules:1.6.1") androidTestImplementation("androidx.test:rules:1.6.1")
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1") androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")

View File

@ -2,7 +2,6 @@ package bou.amine.apps.readerforselfossv2.android
import android.content.Context import android.content.Context
import androidx.annotation.ArrayRes import androidx.annotation.ArrayRes
import androidx.test.espresso.Espresso.onData
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.replaceText import androidx.test.espresso.action.ViewActions.replaceText
@ -15,7 +14,6 @@ import androidx.test.espresso.matcher.ViewMatchers.isNotChecked
import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.espresso.matcher.ViewMatchers.withText
import org.hamcrest.CoreMatchers.allOf import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.Matchers.hasToString
fun performLogin(someUrl: String? = null) { fun performLogin(someUrl: String? = null) {
onView(withId(R.id.urlView)).perform(click()).perform( onView(withId(R.id.urlView)).perform(click()).perform(
@ -24,6 +22,7 @@ fun performLogin(someUrl: String? = null) {
) )
) )
onView(withId(R.id.signInButton)).perform(click()) onView(withId(R.id.signInButton)).perform(click())
Thread.sleep(10000)
} }
fun loginAndInitHome() { fun loginAndInitHome() {
@ -89,21 +88,3 @@ fun testPreferencesFromArray(
onView(withText(res)).check(matches(allOf(isDisplayed(), isChecked()))) onView(withText(res)).check(matches(allOf(isDisplayed(), isChecked())))
} }
} }
fun testAddSourceWithUrl(url: String, sourceName: String) {
onView(withId(R.id.fab))
.perform(click())
onView(withId(R.id.nameInput))
.perform(click()).perform(typeTextIntoFocusedView(sourceName))
onView(withId(R.id.sourceUri))
.perform(click())
.perform(typeTextIntoFocusedView(url))
onView(withId(R.id.tags))
.perform(click()).perform(typeTextIntoFocusedView("tag1,tag2,tag3"))
onView(withId(R.id.spoutsSpinner))
.perform(click())
onData(hasToString("RSS Feed")).perform(click())
onView(withId(R.id.saveBtn))
.perform(click())
onView(withText(sourceName)).check(matches(isDisplayed()))
}

View File

@ -1,6 +1,9 @@
package bou.amine.apps.readerforselfossv2.android package bou.amine.apps.readerforselfossv2.android
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
import androidx.test.espresso.action.ViewActions import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.assertion.ViewAssertions.matches
@ -44,7 +47,9 @@ class HomeActivityTest {
isClickable() isClickable()
) )
) )
openMenu() openActionBarOverflowOrOptionsMenu(
ApplicationProvider.getApplicationContext<Context>()
)
onView(withText(R.string.readAll)).check(matches(isDisplayed())) onView(withText(R.string.readAll)).check(matches(isDisplayed()))
onView(withText(R.string.menu_home_sources)).check(matches(isDisplayed())) onView(withText(R.string.menu_home_sources)).check(matches(isDisplayed()))
onView(withText(R.string.title_activity_settings)).check(matches(isDisplayed())) onView(withText(R.string.title_activity_settings)).check(matches(isDisplayed()))
@ -73,31 +78,43 @@ class HomeActivityTest {
).check(matches(isDisplayed())) ).check(matches(isDisplayed()))
onView(isRoot()).perform(ViewActions.pressBack()) onView(isRoot()).perform(ViewActions.pressBack())
openMenu() openActionBarOverflowOrOptionsMenu(
ApplicationProvider.getApplicationContext<Context>()
)
onView(withText(R.string.readAll)).perform(click()) onView(withText(R.string.readAll)).perform(click())
onView(withText(R.string.markall_dialog_message)).check(matches(isDisplayed())) onView(withText(R.string.markall_dialog_message)).check(matches(isDisplayed()))
onView(isRoot()).perform(ViewActions.pressBack()) onView(isRoot()).perform(ViewActions.pressBack())
openMenu() openActionBarOverflowOrOptionsMenu(
ApplicationProvider.getApplicationContext<Context>()
)
onView(withText(R.string.menu_home_sources)).perform(click()) onView(withText(R.string.menu_home_sources)).perform(click())
onView(withId(R.id.fab)).check(matches(isDisplayed())) onView(withId(R.id.fab)).check(matches(isDisplayed()))
onView(isRoot()).perform(ViewActions.pressBack()) onView(isRoot()).perform(ViewActions.pressBack())
openMenu() openActionBarOverflowOrOptionsMenu(
ApplicationProvider.getApplicationContext<Context>()
)
onView(withText(R.string.title_activity_settings)).perform(click()) onView(withText(R.string.title_activity_settings)).perform(click())
onView(withText(R.string.pref_header_general)).check(matches(isDisplayed())) onView(withText(R.string.pref_header_general)).check(matches(isDisplayed()))
onView(isRoot()).perform(ViewActions.pressBack()) onView(isRoot()).perform(ViewActions.pressBack())
openMenu() openActionBarOverflowOrOptionsMenu(
ApplicationProvider.getApplicationContext<Context>()
)
onView(withText(R.string.menu_home_refresh)).perform(click()) onView(withText(R.string.menu_home_refresh)).perform(click())
onView(withText(R.string.refresh_dialog_message)).check(matches(isDisplayed())) onView(withText(R.string.refresh_dialog_message)).check(matches(isDisplayed()))
onView(isRoot()).perform(ViewActions.pressBack()) onView(isRoot()).perform(ViewActions.pressBack())
openMenu() openActionBarOverflowOrOptionsMenu(
ApplicationProvider.getApplicationContext<Context>()
)
/*onView(withText(R.string.issue_tracker_link)).perform(click()) /*onView(withText(R.string.issue_tracker_link)).perform(click())
onView(withText(R.string.markall_dialog_message)).check(matches(isDisplayed())) onView(withText(R.string.markall_dialog_message)).check(matches(isDisplayed()))
onView(isRoot()).perform(ViewActions.pressBack()) onView(isRoot()).perform(ViewActions.pressBack())
openMenu()*/ openActionBarOverflowOrOptionsMenu(
ApplicationProvider.getApplicationContext<Context>()
)*/
onView(withText(R.string.action_disconnect)).perform(click()) onView(withText(R.string.action_disconnect)).perform(click())
onView(withText(R.string.confirm_disconnect_title)).check(matches(isDisplayed())) onView(withText(R.string.confirm_disconnect_title)).check(matches(isDisplayed()))

View File

@ -6,7 +6,6 @@ import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
import androidx.test.espresso.action.ViewActions import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.replaceText import androidx.test.espresso.action.ViewActions.replaceText
import androidx.test.espresso.action.ViewActions.swipeUp
import androidx.test.espresso.action.ViewActions.typeTextIntoFocusedView import androidx.test.espresso.action.ViewActions.typeTextIntoFocusedView
import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isChecked import androidx.test.espresso.matcher.ViewMatchers.isChecked
@ -103,7 +102,6 @@ class SettingsActivityGeneralTest {
) )
) )
) )
onView(withId(R.id.settings)).perform(swipeUp())
onView(withSettingsCheckboxWidget(R.string.display_all_counts_title)).check( onView(withSettingsCheckboxWidget(R.string.display_all_counts_title)).check(
matches( matches(
allOf( allOf(

View File

@ -1,7 +1,9 @@
package bou.amine.apps.readerforselfossv2.android package bou.amine.apps.readerforselfossv2.android
import android.content.Context import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
@ -31,7 +33,9 @@ class SettingsActivityTest {
context = activity.window.context context = activity.window.context
} }
loginAndInitHome() loginAndInitHome()
openMenu() openActionBarOverflowOrOptionsMenu(
ApplicationProvider.getApplicationContext<Context>()
)
onView(withText(R.string.title_activity_settings)).perform(click()) onView(withText(R.string.title_activity_settings)).perform(click())
} }

View File

@ -1,20 +1,19 @@
package bou.amine.apps.readerforselfossv2.android package bou.amine.apps.readerforselfossv2.android
import androidx.test.espresso.AmbiguousViewMatcherException import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.swipeDown import androidx.test.espresso.action.ViewActions.scrollCompletelyTo
import androidx.test.espresso.assertion.ViewAssertions.doesNotExist import androidx.test.espresso.action.ViewActions.typeTextIntoFocusedView
import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isRoot
import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest import androidx.test.filters.LargeTest
import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
@ -24,6 +23,7 @@ import java.util.UUID
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
@LargeTest @LargeTest
class SourcesActivityTest { class SourcesActivityTest {
@get:Rule @get:Rule
val activityRule = ActivityScenarioRule(LoginActivity::class.java) val activityRule = ActivityScenarioRule(LoginActivity::class.java)
@ -34,50 +34,32 @@ class SourcesActivityTest {
sourceName = UUID.randomUUID().toString().substring(0, 15) sourceName = UUID.randomUUID().toString().substring(0, 15)
loginAndInitHome() loginAndInitHome()
goToSources() openActionBarOverflowOrOptionsMenu(
ApplicationProvider.getApplicationContext<Context>()
)
onView(withText(R.string.menu_home_sources))
.perform(click())
} }
@Test @Test
fun addSource() { fun addSource() {
testAddSourceWithUrl( onView(withId(R.id.fab))
"https://lorem-rss.herokuapp.com/feed?unit=year&interval=1&length=10",
sourceName
)
}
@Test
fun addSourceCheckContent() {
testAddSourceWithUrl("https://news.google.com/rss?hl=en-US&gl=US&ceid=US:en", sourceName)
onView(isRoot()).perform(ViewActions.pressBack())
openMenu()
onView(withText(R.string.menu_home_refresh)).perform(click())
onView(withText(R.string.refresh_dialog_message)).check(matches(isDisplayed()))
onView(
withId(android.R.id.button1)
).perform(click())
Thread.sleep(10000)
onView(withId(R.id.swipeRefreshLayout)).perform(swipeDown())
Thread.sleep(2000)
try {
onView(withId(R.id.sourceTitleAndDate)).check(matches(isDisplayed()))
} catch (e: AmbiguousViewMatcherException) {
assert(true)
}
goToSources()
}
@After
fun deleteTheCreatedSource() {
onView(withText(sourceName)).check(matches(isDisplayed()))
onView(withId(R.id.deleteBtn)).perform(click())
onView(withText(sourceName)).check(doesNotExist())
}
private fun goToSources() {
openMenu()
onView(withText(R.string.menu_home_sources))
.perform(click()) .perform(click())
onView(withId(R.id.nameInput))
.perform(click()).perform(typeTextIntoFocusedView(sourceName))
onView(withId(R.id.sourceUri))
.perform(click())
.perform(typeTextIntoFocusedView("https://lorem-rss.herokuapp.com/feed?unit=year&interval=1&length=10"))
onView(withId(R.id.tags))
.perform(click()).perform(typeTextIntoFocusedView("tag1,tag2,tag3"))
onView(withId(R.id.spoutsSpinner))
.perform(click())
onView(withText("RSS Feed"))
.perform(scrollCompletelyTo())
.perform(click())
onView(withId(R.id.saveBtn))
.perform(click())
onView(withText(sourceName)).check(matches(isDisplayed()))
} }
} }

View File

@ -1,6 +1,5 @@
package bou.amine.apps.readerforselfossv2.android package bou.amine.apps.readerforselfossv2.android
import android.content.Context
import android.view.View import android.view.View
import android.widget.EditText import android.widget.EditText
import android.widget.ImageView import android.widget.ImageView
@ -8,8 +7,6 @@ import android.widget.RelativeLayout
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.drawable.toBitmap
import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
import androidx.test.espresso.Root import androidx.test.espresso.Root
import androidx.test.espresso.matcher.RootMatchers.isPlatformPopup import androidx.test.espresso.matcher.RootMatchers.isPlatformPopup
import androidx.test.espresso.matcher.ViewMatchers.hasSibling import androidx.test.espresso.matcher.ViewMatchers.hasSibling
@ -102,9 +99,3 @@ fun withSettingsCheckboxFrame(@StringRes id: Int): Matcher<View>? {
) )
) )
} }
fun openMenu() {
openActionBarOverflowOrOptionsMenu(
ApplicationProvider.getApplicationContext<Context>()
)
}

View File

@ -16,7 +16,7 @@ class AcraReportingAdministrator : ReportingAdministrator {
config: CoreConfiguration, config: CoreConfiguration,
reportBuilder: ReportBuilder reportBuilder: ReportBuilder
): Boolean { ): Boolean {
return reportBuilder.exception !is DeadSystemException && (reportBuilder.exception != null && reportBuilder.exception!!::class.simpleName != "CannotDeliverBroadcastException") return reportBuilder.exception !is DeadSystemException
} }
override fun shouldSendReport( override fun shouldSendReport(

View File

@ -23,7 +23,6 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical" android:orientation="vertical"
android:padding="@dimen/activity_horizontal_margin"> android:padding="@dimen/activity_horizontal_margin">
<!-- Login progress --> <!-- Login progress -->
@ -38,7 +37,7 @@
<LinearLayout <LinearLayout
android:id="@+id/loginForm" android:id="@+id/loginForm"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<EditText <EditText

View File

@ -1,3 +1,10 @@
buildscript {
dependencies {
// SqlDelight
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("8.7.3").apply(false) id("com.android.application").version("8.7.3").apply(false)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 294 KiB

View File

@ -1,18 +1,18 @@
val ktorVersion = "3.0.3" val ktorVersion = "2.3.2"
object SqlDelight { object SqlDelight {
const val runtime = "app.cash.sqldelight:runtime:2.0.2" const val runtime = "com.squareup.sqldelight:runtime:1.5.4"
const val android = "app.cash.sqldelight:android-driver:2.0.2" const val android = "com.squareup.sqldelight:android-driver:1.5.4"
const val native = "app.cash.sqldelight:native-driver:2.0.2" const val native = "com.squareup.sqldelight:native-driver:1.5.4"
} }
plugins { plugins {
kotlin("multiplatform") kotlin("multiplatform")
id("com.android.library") id("com.android.library")
id("com.squareup.sqldelight")
kotlin("plugin.serialization") version "1.9.0" kotlin("plugin.serialization") version "1.9.0"
id("org.jetbrains.kotlinx.kover") id("org.jetbrains.kotlinx.kover")
id("app.cash.sqldelight") version "2.0.2"
} }
kotlin { kotlin {
@ -37,7 +37,7 @@ kotlin {
implementation("io.ktor:ktor-client-logging:$ktorVersion") implementation("io.ktor:ktor-client-logging:$ktorVersion")
implementation("io.ktor:ktor-client-auth:$ktorVersion") implementation("io.ktor:ktor-client-auth:$ktorVersion")
implementation("io.ktor:ktor-client-cio:$ktorVersion") implementation("io.ktor:ktor-client-cio:$ktorVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.1") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1")
implementation("org.jsoup:jsoup:1.15.4") implementation("org.jsoup:jsoup:1.15.4")
@ -66,6 +66,7 @@ kotlin {
val androidMain by getting { val androidMain by getting {
dependencies { dependencies {
implementation("com.squareup.okhttp3:okhttp:4.11.0") 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
@ -85,6 +86,7 @@ kotlin {
dependencies { dependencies {
implementation(SqlDelight.native) implementation(SqlDelight.native)
implementation("io.ktor:ktor-client-ios:2.1.1")
} }
} }
val iosX64Test by getting val iosX64Test by getting
@ -108,10 +110,11 @@ android {
namespace = "bou.amine.apps.readerforselfossv2" namespace = "bou.amine.apps.readerforselfossv2"
} }
sqldelight { sqldelight {
databases { database("ReaderForSelfossDB") {
create("ReaderForSelfossDB") { packageName = "bou.amine.apps.readerforselfossv2.dao"
packageName.set("bou.amine.apps.readerforselfossv2.dao") sourceFolders = listOf("sqldelight")
}
} }
} }

View File

@ -1,15 +1,10 @@
package bou.amine.apps.readerforselfossv2.dao package bou.amine.apps.readerforselfossv2.dao
import android.content.Context import android.content.Context
import app.cash.sqldelight.db.SqlDriver import com.squareup.sqldelight.android.AndroidSqliteDriver
import app.cash.sqldelight.driver.android.AndroidSqliteDriver import com.squareup.sqldelight.db.SqlDriver
actual class DriverFactory(private val context: Context) { actual class DriverFactory(private val context: Context) {
actual fun createDriver(): SqlDriver { actual fun createDriver(): SqlDriver {
return AndroidSqliteDriver( return AndroidSqliteDriver(ReaderForSelfossDB.Schema, context, "ReaderForSelfossV2-android.db")
ReaderForSelfossDB.Schema,
context,
"ReaderForSelfossV2-android.db"
)
} }
} }

View File

@ -1,6 +1,6 @@
package bou.amine.apps.readerforselfossv2.dao package bou.amine.apps.readerforselfossv2.dao
import app.cash.sqldelight.db.SqlDriver import com.squareup.sqldelight.db.SqlDriver
expect class DriverFactory { expect class DriverFactory {
fun createDriver(): SqlDriver fun createDriver(): SqlDriver

View File

@ -1,26 +1,22 @@
package bou.amine.apps.readerforselfossv2.rest package bou.amine.apps.readerforselfossv2.rest
import bou.amine.apps.readerforselfossv2.model.MercuryModel import bou.amine.apps.readerforselfossv2.model.*
import bou.amine.apps.readerforselfossv2.model.StatusAndData
import io.github.aakira.napier.Napier import io.github.aakira.napier.Napier
import io.ktor.client.HttpClient import io.ktor.client.*
import io.ktor.client.plugins.cache.HttpCache import io.ktor.client.plugins.cache.*
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.plugins.logging.LogLevel import io.ktor.client.plugins.logging.*
import io.ktor.client.plugins.logging.Logger import io.ktor.client.request.*
import io.ktor.client.plugins.logging.Logging import io.ktor.serialization.kotlinx.json.*
import io.ktor.client.request.get
import io.ktor.client.request.parameter
import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
class MercuryApi { class MercuryApi() {
var client = createHttpClient() var client = createHttpClient()
private fun createHttpClient(): HttpClient { private fun createHttpClient(): HttpClient {
return HttpClient { return HttpClient {
install(HttpCache)
install(ContentNegotiation) { install(ContentNegotiation) {
install(HttpCache)
json( json(
Json { Json {
prettyPrint = true prettyPrint = true

View File

@ -45,8 +45,8 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
setupInsecureHTTPEngine(this) setupInsecureHTTPEngine(this)
} }
} }
install(HttpCache)
install(ContentNegotiation) { install(ContentNegotiation) {
install(HttpCache)
json( json(
Json { Json {
prettyPrint = true prettyPrint = true
@ -105,8 +105,8 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
private fun hasLoginInfo() = private fun hasLoginInfo() =
appSettingsService.getUserName().isNotEmpty() && appSettingsService.getUserName().isNotEmpty() &&
appSettingsService.getPassword() appSettingsService.getPassword()
.isNotEmpty() .isNotEmpty()
suspend fun login(): SuccessResponse = suspend fun login(): SuccessResponse =
if (appSettingsService.getUserName().isNotEmpty() && if (appSettingsService.getUserName().isNotEmpty() &&
@ -127,9 +127,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
client.tryToGet(url("/login")) { client.tryToGet(url("/login")) {
parameter("username", appSettingsService.getUserName()) parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword()) parameter("password", appSettingsService.getPassword())
if (appSettingsService.getBasicUserName() if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
) {
headers { headers {
append( append(
HttpHeaders.Authorization, HttpHeaders.Authorization,
@ -150,9 +148,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
client.tryToPost(url("/login")) { client.tryToPost(url("/login")) {
parameter("username", appSettingsService.getUserName()) parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword()) parameter("password", appSettingsService.getPassword())
if (appSettingsService.getBasicUserName() if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
) {
headers { headers {
append( append(
HttpHeaders.Authorization, HttpHeaders.Authorization,
@ -168,8 +164,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
}, },
) )
private fun shouldHaveNewLogout() = private fun shouldHaveNewLogout() = appSettingsService.getApiVersion() >= 5 // We are missing 4.1.0
appSettingsService.getApiVersion() >= 5 // We are missing 4.1.0
suspend fun logout(): SuccessResponse = suspend fun logout(): SuccessResponse =
if (shouldHaveNewLogout()) { if (shouldHaveNewLogout()) {
@ -181,9 +176,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
private suspend fun maybeLogoutIfAvailable() = private suspend fun maybeLogoutIfAvailable() =
responseOrSuccessIf404( responseOrSuccessIf404(
client.tryToGet(url("/logout")) { client.tryToGet(url("/logout")) {
if (appSettingsService.getBasicUserName() if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
) {
headers { headers {
append( append(
HttpHeaders.Authorization, HttpHeaders.Authorization,
@ -202,9 +195,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
private suspend fun doLogout() = private suspend fun doLogout() =
maybeResponse( maybeResponse(
client.tryToDelete(url("/api/session/current")) { client.tryToDelete(url("/api/session/current")) {
if (appSettingsService.getBasicUserName() if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
) {
headers { headers {
append( append(
HttpHeaders.Authorization, HttpHeaders.Authorization,
@ -242,9 +233,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
parameter("updatedsince", updatedSince) parameter("updatedsince", updatedSince)
parameter("items", items ?: appSettingsService.getItemsNumber()) parameter("items", items ?: appSettingsService.getItemsNumber())
parameter("offset", offset) parameter("offset", offset)
if (appSettingsService.getBasicUserName() if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
) {
headers { headers {
append( append(
HttpHeaders.Authorization, HttpHeaders.Authorization,
@ -269,9 +258,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
} }
parameter("type", "all") parameter("type", "all")
parameter("items", 1) parameter("items", 1)
if (appSettingsService.getBasicUserName() if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
) {
headers { headers {
append( append(
HttpHeaders.Authorization, HttpHeaders.Authorization,
@ -294,9 +281,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
parameter("username", appSettingsService.getUserName()) parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword()) parameter("password", appSettingsService.getPassword())
} }
if (appSettingsService.getBasicUserName() if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
) {
headers { headers {
append( append(
HttpHeaders.Authorization, HttpHeaders.Authorization,
@ -319,9 +304,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
parameter("username", appSettingsService.getUserName()) parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword()) parameter("password", appSettingsService.getPassword())
} }
if (appSettingsService.getBasicUserName() if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
) {
headers { headers {
append( append(
HttpHeaders.Authorization, HttpHeaders.Authorization,
@ -344,9 +327,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
parameter("username", appSettingsService.getUserName()) parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword()) parameter("password", appSettingsService.getPassword())
} }
if (appSettingsService.getBasicUserName() if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
) {
headers { headers {
append( append(
HttpHeaders.Authorization, HttpHeaders.Authorization,
@ -369,9 +350,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
parameter("username", appSettingsService.getUserName()) parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword()) parameter("password", appSettingsService.getPassword())
} }
if (appSettingsService.getBasicUserName() if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
) {
headers { headers {
append( append(
HttpHeaders.Authorization, HttpHeaders.Authorization,
@ -394,9 +373,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
parameter("username", appSettingsService.getUserName()) parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword()) parameter("password", appSettingsService.getPassword())
} }
if (appSettingsService.getBasicUserName() if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
) {
headers { headers {
append( append(
HttpHeaders.Authorization, HttpHeaders.Authorization,
@ -419,9 +396,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
parameter("username", appSettingsService.getUserName()) parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword()) parameter("password", appSettingsService.getPassword())
} }
if (appSettingsService.getBasicUserName() if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
) {
headers { headers {
append( append(
HttpHeaders.Authorization, HttpHeaders.Authorization,
@ -440,9 +415,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
suspend fun apiInformation(): StatusAndData<SelfossModel.ApiInformation> = suspend fun apiInformation(): StatusAndData<SelfossModel.ApiInformation> =
bodyOrFailure( bodyOrFailure(
client.tryToGet(url("/api/about")) { client.tryToGet(url("/api/about")) {
if (appSettingsService.getBasicUserName() if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
) {
headers { headers {
append( append(
HttpHeaders.Authorization, HttpHeaders.Authorization,
@ -465,9 +438,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
parameter("username", appSettingsService.getUserName()) parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword()) parameter("password", appSettingsService.getPassword())
} }
if (appSettingsService.getBasicUserName() if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
) {
headers { headers {
append( append(
HttpHeaders.Authorization, HttpHeaders.Authorization,
@ -490,9 +461,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
parameter("username", appSettingsService.getUserName()) parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword()) parameter("password", appSettingsService.getPassword())
} }
if (appSettingsService.getBasicUserName() if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
) {
headers { headers {
append( append(
HttpHeaders.Authorization, HttpHeaders.Authorization,
@ -515,9 +484,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
parameter("username", appSettingsService.getUserName()) parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword()) parameter("password", appSettingsService.getPassword())
} }
if (appSettingsService.getBasicUserName() if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
) {
headers { headers {
append( append(
HttpHeaders.Authorization, HttpHeaders.Authorization,
@ -540,9 +507,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
parameter("username", appSettingsService.getUserName()) parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword()) parameter("password", appSettingsService.getPassword())
} }
if (appSettingsService.getBasicUserName() if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
) {
headers { headers {
append( append(
HttpHeaders.Authorization, HttpHeaders.Authorization,
@ -563,17 +528,15 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
client.tryToSubmitForm( client.tryToSubmitForm(
url = url("/mark"), url = url("/mark"),
formParameters = formParameters =
Parameters.build { Parameters.build {
if (!shouldHavePostLogin()) { if (!shouldHavePostLogin()) {
append("username", appSettingsService.getUserName()) append("username", appSettingsService.getUserName())
append("password", appSettingsService.getPassword()) append("password", appSettingsService.getPassword())
} }
ids.map { append("ids[]", it) } ids.map { append("ids[]", it) }
}, },
block = { block = {
if (appSettingsService.getBasicUserName() if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
) {
headers { headers {
append( append(
HttpHeaders.Authorization, HttpHeaders.Authorization,
@ -614,20 +577,18 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
client.tryToSubmitForm( client.tryToSubmitForm(
url = url("/source"), url = url("/source"),
formParameters = formParameters =
Parameters.build { Parameters.build {
if (!shouldHavePostLogin()) { if (!shouldHavePostLogin()) {
append("username", appSettingsService.getUserName()) append("username", appSettingsService.getUserName())
append("password", appSettingsService.getPassword()) append("password", appSettingsService.getPassword())
} }
append("title", title) append("title", title)
append("url", url) append("url", url)
append("spout", spout) append("spout", spout)
append(tagsParamName, tags) append(tagsParamName, tags)
}, },
block = { block = {
if (appSettingsService.getBasicUserName() if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
) {
headers { headers {
append( append(
HttpHeaders.Authorization, HttpHeaders.Authorization,
@ -669,20 +630,18 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
client.tryToSubmitForm( client.tryToSubmitForm(
url = url("/source/$id"), url = url("/source/$id"),
formParameters = formParameters =
Parameters.build { Parameters.build {
if (!shouldHavePostLogin()) { if (!shouldHavePostLogin()) {
append("username", appSettingsService.getUserName()) append("username", appSettingsService.getUserName())
append("password", appSettingsService.getPassword()) append("password", appSettingsService.getPassword())
} }
append("title", title) append("title", title)
append("url", url) append("url", url)
append("spout", spout) append("spout", spout)
append(tagsParamName, tags) append(tagsParamName, tags)
}, },
block = { block = {
if (appSettingsService.getBasicUserName() if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
) {
headers { headers {
append( append(
HttpHeaders.Authorization, HttpHeaders.Authorization,
@ -705,9 +664,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
parameter("username", appSettingsService.getUserName()) parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword()) parameter("password", appSettingsService.getPassword())
} }
if (appSettingsService.getBasicUserName() if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
) {
headers { headers {
append( append(
HttpHeaders.Authorization, HttpHeaders.Authorization,

View File

@ -1,5 +1,3 @@
import kotlin.Boolean;
CREATE TABLE `ACTION` ( CREATE TABLE `ACTION` (
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
`articleid` TEXT NOT NULL, `articleid` TEXT NOT NULL,

View File

@ -1,5 +1,3 @@
import kotlin.Boolean;
CREATE TABLE ITEM ( CREATE TABLE ITEM (
`id` TEXT NOT NULL, `id` TEXT NOT NULL,
`datetime` TEXT NOT NULL, `datetime` TEXT NOT NULL,

View File

@ -1,7 +1,7 @@
package bou.amine.apps.readerforselfossv2.dao package bou.amine.apps.readerforselfossv2.dao
import app.cash.sqldelight.db.SqlDriver import com.squareup.sqldelight.db.SqlDriver
import app.cash.sqldelight.driver.native.NativeSqliteDriver import com.squareup.sqldelight.drivers.native.NativeSqliteDriver
actual class DriverFactory { actual class DriverFactory {
actual fun createDriver(): SqlDriver { actual fun createDriver(): SqlDriver {

View File

@ -1,7 +1,7 @@
package bou.amine.apps.readerforselfossv2.dao package bou.amine.apps.readerforselfossv2.dao
import app.cash.sqldelight.db.SqlDriver import com.squareup.sqldelight.db.SqlDriver
import app.cash.sqldelight.driver.native.NativeSqliteDriver import com.squareup.sqldelight.drivers.native.NativeSqliteDriver
actual class DriverFactory { actual class DriverFactory {
actual fun createDriver(): SqlDriver { actual fun createDriver(): SqlDriver {