diff --git a/.drone.yml b/.drone.yml index 2e44be0..6e8ac31 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,26 +1,118 @@ kind: pipeline type: docker -name: android +name: test steps: - - name: code-analysis + - name: AnylyseBuildTest image: mingc/android-build-box:latest failure: ignore commands: - - ls -la + - echo "---------------------------------------------------------" + - echo "Analysing..." - ./gradlew sonarqube -Dsonar.projectKey=RFS2 -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN -PignoreGitVersion=true -P appLoginUrl="\"URL\"" -P appLoginUsername="\"LOGIN\"" -P appLoginPassword="\"PASS\"" + - echo "---------------------------------------------------------" + - echo "Building..." + - ./gradlew :androidApp:build -PignoreGitVersion=true -P appLoginUrl="\"URL\"" -P appLoginUsername="\"LOGIN\"" -P appLoginPassword="\"PASS\"" -P pushCache=false + - echo "---------------------------------------------------------" + - echo "Testing..." + - echo "---------------------------------------------------------" + - ./gradlew test -PignoreGitVersion=true -P appLoginUrl="\"URL\"" -P appLoginUsername="\"LOGIN\"" -P appLoginPassword="\"PASS\"" -P pushCache=false environment: SONAR_HOST_URL: from_secret: sonarScannerHostUrl SONAR_LOGIN: from_secret: sonarScannerLogin +trigger: + event: + - push + - pull_request - - name: test - image: mingc/android-build-box:latest +--- +kind: pipeline +type: docker +name: Publish + +steps: + - name: createTag + image: ubuntu:latest commands: - - ./gradlew test -PignoreGitVersion=true -P appLoginUrl="\"URL\"" -P appLoginUsername="\"LOGIN\"" -P appLoginPassword="\"PASS\"" -P pushCache=false + - apt-get update && apt-get install -y git + - ./build.sh --publish --from-ci + - git remote add pushing https://$GITEA_USR:$GITEA_PASS@gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform.git + - git push pushing --tags + environment: + GITEA_USR: + from_secret: giteaUsr + GITEA_PASS: + from_secret: giteaPass + - name: scpFiles + image: appleboy/drone-scp + settings: + host: amine-louveau.fr + username: ubuntu + key: + from_secret: privateKey + port: 22 + target: /home/ubuntu/ + source: version.txt + + - name: deploy + image: appleboy/drone-ssh + settings: + host: amine-louveau.fr + user: ubuntu + key: + from_secret: privateKey + command_timeout: 2m + script: + - cd /home/ubuntu + - sudo rm -rf /var/www/amine/version.txt + - sudo chown www-data:www-data ./version.txt + - sudo mv version.txt /var/www/amine/ + +trigger: + event: + - promote + target: + - production + +--- +kind: pipeline +type: docker +name: Release + +steps: - name: build image: mingc/android-build-box:latest commands: - - ./gradlew :androidApp:build -PignoreGitVersion=true -P appLoginUrl="\"URL\"" -P appLoginUsername="\"LOGIN\"" -P appLoginPassword="\"PASS\"" -P pushCache=false \ No newline at end of file + - echo "Generate APK" + - ./gradlew :androidApp:assembleGithubConfigRelease -PignoreGitVersion=true -P appLoginUrl="\"URL\"" -P appLoginUsername="\"LOGIN\"" -P appLoginPassword="\"PASS\"" -P pushCache=false + - echo "---------------------------------------------------------" + - echo "Get Key" + - wget https://amine-louveau.fr/key + - echo "---------------------------------------------------------" + - echo "Zipalign" + - $ANDROID_HOME/build-tools/31.0.0/zipalign -f -v 4 androidApp/build/outputs/apk/githubConfig/release/androidApp-githubConfig-release-unsigned.apk androidApp/build/outputs/apk/githubConfig/release/android-prod-released-ziped.apk + - echo "---------------------------------------------------------" + - echo "Sign" + - $ANDROID_HOME/build-tools/31.0.0/apksigner sign -v --out signed.apk --ks ./key --ks-key-alias $YOUR_KEY_ALIAS --ks-pass pass:$YOUR_KEYSTORE_PASSWORD --v1-signing-enabled true --v2-signing-enabled true androidApp/build/outputs/apk/githubConfig/release/android-prod-released-ziped.apk + - echo "---------------------------------------------------------" + - echo "Verify" + - $ANDROID_HOME/build-tools/31.0.0/apksigner verify signed.apk + environment: + YOUR_KEYSTORE_PASSWORD: + from_secret: keyPass + YOUR_KEY_ALIAS: + from_secret: keyAlias + + - name: gitea_release + image: plugins/gitea-release + settings: + api_key: + from_secret: giteaAPI + base_url: https://gitea.amine-louveau.fr + files: signed.apk +trigger: + event: + - tag \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index a87b03b..aef1896 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +# V2/Multiplatform rewrite + +**v1** + +- The app has the same functionalities as before. + + +-------------------------------------------------------------------- + +# Old version changes + **1.7.x** - Hiding tags with 0 articles diff --git a/README.md b/README.md index f9166ad..338b57f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ReaderForSelfoss-multiplatform +# ReaderForSelfoss-multiplatform [![Build Status](https://build.amine-louveau.fr/api/badges/Louvorg/ReaderForSelfoss-multiplatform/status.svg)](https://build.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform) [![Crowdin](https://d322cqt584bo4o.cloudfront.net/readerforselfoss/localized.svg)](https://crowdin.com/project/readerforselfoss) diff --git a/androidApp/build.gradle.kts b/androidApp/build.gradle.kts index 846f050..ed8c38c 100644 --- a/androidApp/build.gradle.kts +++ b/androidApp/build.gradle.kts @@ -93,7 +93,7 @@ android { } flavorDimensions.add("build") productFlavors { - create("github") { + create("githubConfig") { versionNameSuffix = "-github" dimension = "build" } @@ -101,6 +101,7 @@ android { kotlinOptions { jvmTarget = "1.8" } + namespace = "bou.amine.apps.readerforselfossv2.android" } diff --git a/androidApp/proguard-rules.pro b/androidApp/proguard-rules.pro index 0b3ee7b..f7c7eff 100644 --- a/androidApp/proguard-rules.pro +++ b/androidApp/proguard-rules.pro @@ -55,11 +55,38 @@ public *; } --dontwarn com.anupcowkur.reservoir.** - -dontwarn javax.annotation.** -keep class android.support.v7.widget.SearchView { *; } # maybe remove later ? -keep class * extends androidx.fragment.app.Fragment + + +# Keep `Companion` object fields of serializable classes. +# This avoids serializer lookup through `getDeclaredClasses` as done for named companion objects. +-if @kotlinx.serialization.Serializable class ** +-keepclassmembers class <1> { + static <1>$Companion Companion; +} + +# Keep `serializer()` on companion objects (both default and named) of serializable classes. +-if @kotlinx.serialization.Serializable class ** { + static **$* *; +} +-keepclassmembers class <2>$<3> { + kotlinx.serialization.KSerializer serializer(...); +} + +# Keep `INSTANCE.serializer()` of serializable objects. +-if @kotlinx.serialization.Serializable class ** { + public static ** INSTANCE; +} +-keepclassmembers class <1> { + public static <1> INSTANCE; + kotlinx.serialization.KSerializer serializer(...); +} + +# @Serializable and @Polymorphic are used at runtime for polymorphic serialization. +-keepattributes RuntimeVisibleAnnotations,AnnotationDefault + diff --git a/androidApp/publish-version.sh b/androidApp/publish-version.sh deleted file mode 100755 index 4d4c5b9..0000000 --- a/androidApp/publish-version.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -# NOTE: This is copy/pasted in jenkins - -rm -f version.txt -printf "versionName=$1-github\nversionCode=$1" >> version.txt - -# You'll need to change server as your server and define a VERSION_PATH. -scp version.txt server:$VERSION_PATH - -rm version.txt diff --git a/androidApp/src/main/AndroidManifest.xml b/androidApp/src/main/AndroidManifest.xml index d46e8e1..21addf9 100644 --- a/androidApp/src/main/AndroidManifest.xml +++ b/androidApp/src/main/AndroidManifest.xml @@ -1,7 +1,6 @@ + xmlns:tools="http://schemas.android.com/tools"> diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/AddSourceActivity.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/AddSourceActivity.kt index 0c7093c..3cec99e 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/AddSourceActivity.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/AddSourceActivity.kt @@ -9,7 +9,7 @@ import androidx.constraintlayout.widget.ConstraintLayout import bou.amine.apps.readerforselfossv2.android.databinding.ActivityAddSourceBinding import bou.amine.apps.readerforselfossv2.android.themes.AppColors import bou.amine.apps.readerforselfossv2.android.themes.Toppings -import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlValid +import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlInvalid import bou.amine.apps.readerforselfossv2.model.NetworkUnavailableException import bou.amine.apps.readerforselfossv2.repository.Repository import bou.amine.apps.readerforselfossv2.service.AppSettingsService @@ -84,7 +84,7 @@ class AddSourceActivity : AppCompatActivity(), DIAware { super.onResume() val baseUrl = appSettingsService.getBaseUrl() - if (baseUrl.isEmpty() || !baseUrl.isBaseUrlValid(this@AddSourceActivity)) { + if (baseUrl.isEmpty() || baseUrl.isBaseUrlInvalid(this@AddSourceActivity)) { mustLoginToAddSource() } else { handleSpoutsSpinner(binding.spoutsSpinner, binding.progress, binding.formContainer) diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/LoginActivity.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/LoginActivity.kt index c7883b6..bc32762 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/LoginActivity.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/LoginActivity.kt @@ -14,7 +14,7 @@ import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import bou.amine.apps.readerforselfossv2.android.databinding.ActivityLoginBinding import bou.amine.apps.readerforselfossv2.android.themes.AppColors -import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlValid +import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlInvalid import bou.amine.apps.readerforselfossv2.repository.Repository import bou.amine.apps.readerforselfossv2.service.AppSettingsService import com.mikepenz.aboutlibraries.LibsBuilder @@ -115,14 +115,14 @@ class LoginActivity : AppCompatActivity(), DIAware { binding.passwordView.error = null // Store values at the time of the login attempt. - val url = binding.urlView.text.toString() - val login = binding.loginView.text.toString() - val password = binding.passwordView.text.toString() + val url = binding.urlView.text.toString().trim() + val login = binding.loginView.text.toString().trim() + val password = binding.passwordView.text.toString().trim() var cancel = false var focusView: View? = null - if (!url.isBaseUrlValid(this@LoginActivity)) { + if (url.isBaseUrlInvalid(this@LoginActivity)) { binding.urlView.error = getString(R.string.login_url_problem) focusView = binding.urlView cancel = true diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/ArticleFragment.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/ArticleFragment.kt index f50786a..7e24dea 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/ArticleFragment.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/ArticleFragment.kt @@ -268,8 +268,8 @@ class ArticleFragment : Fragment(), DIAware { private fun getContentFromMercury(customTabsIntent: CustomTabsIntent) { if (repository.isNetworkAvailable()) { - binding.progressBar.visibility = View.VISIBLE - val parser = MercuryApi() + binding.progressBar.visibility = View.VISIBLE + val parser = MercuryApi() parser.parseUrl(url).enqueue( object : Callback { diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/LinksUtils.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/LinksUtils.kt index 1fc608a..600864b 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/LinksUtils.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/LinksUtils.kt @@ -163,7 +163,7 @@ private fun openInBrowser(linkDecoded: String, app: Activity) { fun String.isUrlValid(): Boolean = this.toHttpUrlOrNull() != null && Patterns.WEB_URL.matcher(this).matches() -fun String.isBaseUrlValid(ctx: Context): Boolean { +fun String.isBaseUrlInvalid(ctx: Context): Boolean { val baseUrl = this.toHttpUrlOrNull() var existsAndEndsWithSlash = false if (baseUrl != null) { @@ -171,7 +171,7 @@ fun String.isBaseUrlValid(ctx: Context): Boolean { existsAndEndsWithSlash = "" == pathSegments[pathSegments.size - 1] } - return Patterns.WEB_URL.matcher(this).matches() && existsAndEndsWithSlash + return !(Patterns.WEB_URL.matcher(this).matches() && existsAndEndsWithSlash) } fun Context.openInBrowserAsNewTask(i: SelfossModel.Item) { diff --git a/androidApp/version.sh b/androidApp/version.sh deleted file mode 100755 index 3bb4033..0000000 --- a/androidApp/version.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash -# You can pass --force as first parameter to force push and tag creation. - -echo "Creating tag $@" - -TAG="v$@" -git tag ${TAG} - -echo "Pushing tag" - -git push origin ${TAG} diff --git a/build.gradle.kts b/build.gradle.kts index 8be2b47..27db859 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ buildscript { } dependencies { classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10") - classpath("com.android.tools.build:gradle:7.2.2") + classpath("com.android.tools.build:gradle:7.3.0") // sonarquve classpath("org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.4.0.2513") diff --git a/androidApp/build.sh b/build.sh similarity index 85% rename from androidApp/build.sh rename to build.sh index abdea4d..c68baad 100755 --- a/androidApp/build.sh +++ b/build.sh @@ -2,7 +2,7 @@ git fetch --tags -p -BASE_VERSION="1.7" +BASE_VERSION="1" LAST_TAG=$(git tag -l | sort -V | tail -1) INITIAL_VERSION="${BASE_VERSION//./}$(date '+%y%m%j')" @@ -21,11 +21,11 @@ VERSION="${INITIAL_VERSION}${TODAYS_VERSION}" PARAMS_EXCEPT_PUBLISH=$(echo $1 | sed 's/\-\-publish//') -./version.sh ${VERSION} ${PARAMS_EXCEPT_PUBLISH} +./version.sh ${VERSION} ${PARAMS_EXCEPT_PUBLISH} $@ if [[ "$@" == *'--publish'* ]] then - ./publish-version.sh ${VERSION} + ./publish-version.sh ${VERSION} $@ else echo "Did not publish. If you wanted to do so, call the script with \"--publish\" or \"--publish-local\"." fi diff --git a/fastlane/metadata/android/en-US/short_description.txt b/fastlane/metadata/android/en-US/short_description.txt index 543c361..655b51a 100644 --- a/fastlane/metadata/android/en-US/short_description.txt +++ b/fastlane/metadata/android/en-US/short_description.txt @@ -1,3 +1 @@ A new RSS reader for selfoss. - -It connects to your selfoss instance (works only with selfoss, and can't work without it), and you'll be able to read and manage all your RSS feeds. diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e4ddf24..b8c9f22 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Wed Feb 09 17:05:19 CET 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/publish-version.sh b/publish-version.sh new file mode 100755 index 0000000..38ed8a6 --- /dev/null +++ b/publish-version.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# NOTE: This is copy/pasted in jenkins + +rm -f version.txt +printf "versionName=$1-github\nversionCode=$1" >> version.txt + +if [[ "$@" == *'--from-ci'* ]] +then + echo "File created. HANDLE IN CI" +else + # You'll need to change server as your server and define a VERSION_PATH. + scp version.txt server:$VERSION_PATH + rm version.txt +fi diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index e2f6b91..e351189 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -114,6 +114,7 @@ android { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } + namespace = "bou.amine.apps.readerforselfossv2" } sqldelight { diff --git a/shared/src/androidMain/AndroidManifest.xml b/shared/src/androidMain/AndroidManifest.xml index 1829faa..568741e 100644 --- a/shared/src/androidMain/AndroidManifest.xml +++ b/shared/src/androidMain/AndroidManifest.xml @@ -1,2 +1,2 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/model/SelfossModel.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/model/SelfossModel.kt index 15decfb..e392786 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/model/SelfossModel.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/model/SelfossModel.kt @@ -105,4 +105,16 @@ class SelfossModel { return this } } + + class StatusAndData(val success: Boolean, val data: T? = null) { + companion object { + fun succes(d: T): StatusAndData { + return StatusAndData(true, d) + } + + fun error(): StatusAndData { + return StatusAndData(false) + } + } + } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt index f1add44..65fbfbe 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt @@ -48,7 +48,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap suspend fun getNewerItems(): ArrayList { // TODO: Use the updatedSince parameter - var fetchedItems: List? = null + var fetchedItems: SelfossModel.StatusAndData> = SelfossModel.StatusAndData.error() if (isNetworkAvailable()) { fetchedItems = api.getItems( displayedItems.type, @@ -60,23 +60,25 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap ) } else { if (appSettingsService.isItemCachingEnabled()) { - fetchedItems = getDBItems().filter { - displayedItems == ItemType.ALL || - (it.unread && displayedItems == ItemType.UNREAD) || - (it.starred && displayedItems == ItemType.STARRED) - }.map { it.toView() } + fetchedItems = SelfossModel.StatusAndData.succes( + getDBItems().filter { + displayedItems == ItemType.ALL || + (it.unread && displayedItems == ItemType.UNREAD) || + (it.starred && displayedItems == ItemType.STARRED) + }.map { it.toView() } + ) } } - if (fetchedItems != null) { - items = ArrayList(fetchedItems) + if (fetchedItems.success && fetchedItems.data != null) { + items = ArrayList(fetchedItems.data!!) sortItems() } return items } suspend fun getOlderItems(): ArrayList { - var fetchedItems: List? = null + var fetchedItems: SelfossModel.StatusAndData> = SelfossModel.StatusAndData.error() if (isNetworkAvailable()) { val offset = items.size fetchedItems = api.getItems( @@ -89,16 +91,16 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap ) } // When using the db cache, we load everything the first time, so there should be nothing more to load. - if (fetchedItems != null) { - items.addAll(fetchedItems) + if (fetchedItems.success && fetchedItems.data != null) { + items.addAll(fetchedItems.data!!) sortItems() } return items } - private suspend fun getMaxItemsForBackground(itemType: ItemType): List? { + private suspend fun getMaxItemsForBackground(itemType: ItemType): List { return if (isNetworkAvailable()) { - api.getItems( + val items = api.getItems( itemType.type, 0, tagFilter?.tag, @@ -107,6 +109,11 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap null, 200 ) + return if (items.success && items.data != null) { + items.data + } else { + emptyList() + } } else { emptyList() } @@ -120,10 +127,10 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap var success = false if (isNetworkAvailable()) { val response = api.stats() - if (response != null) { - badgeUnread = response.unread - badgeAll = response.total - badgeStarred = response.starred + if (response.success && response.data != null) { + badgeUnread = response.data.unread + badgeAll = response.data.total + badgeStarred = response.data.starred success = true } } else { @@ -139,10 +146,10 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap suspend fun getTags(): List? { return if (isNetworkAvailable()) { val apiTags = api.tags() - if (apiTags != null && (appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled())) { - resetDBTagsWithData(apiTags) + if (apiTags.success && apiTags.data != null && (appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled())) { + resetDBTagsWithData(apiTags.data) } - apiTags + apiTags.data } else { getDBTags().map { it.toView() } } @@ -151,7 +158,12 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap // TODO: Add tests suspend fun getSpouts(): Map? { return if (isNetworkAvailable()) { - api.spouts() + val spouts = api.spouts() + return if (spouts.success && spouts.data != null) { + spouts.data + } else { + emptyMap() // TODO: do something here + } } else { throw NetworkUnavailableException() } @@ -160,10 +172,10 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap suspend fun getSources(): ArrayList? { return if (isNetworkAvailable()) { val apiSources = api.sources() - if (apiSources != null && (appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled())) { - resetDBSourcesWithData(apiSources) + if (apiSources.success && apiSources.data != null && (appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled())) { + resetDBSourcesWithData(apiSources.data) } - apiSources + apiSources.data } else { ArrayList(getDBSources().map { it.toView() }) } @@ -180,7 +192,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap private suspend fun markAsReadById(id: Int): Boolean { return if (isNetworkAvailable()) { - api.markAsRead(id.toString())?.isSuccess == true + api.markAsRead(id.toString())?.isSuccess } else { insertDBAction(id.toString(), read = true) true @@ -199,7 +211,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap private suspend fun unmarkAsReadById(id: Int): Boolean { return if (isNetworkAvailable()) { - api.unmarkAsRead(id.toString())?.isSuccess == true + api.unmarkAsRead(id.toString())?.isSuccess } else { insertDBAction(id.toString(), unread = true) true @@ -217,7 +229,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap private suspend fun starrById(id: Int): Boolean { return if (isNetworkAvailable()) { - api.starr(id.toString())?.isSuccess == true + api.starr(id.toString())?.isSuccess } else { insertDBAction(id.toString(), starred = true) true @@ -235,7 +247,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap private suspend fun unstarrById(id: Int): Boolean { return if (isNetworkAvailable()) { - api.unstarr(id.toString())?.isSuccess == true + api.unstarr(id.toString())?.isSuccess } else { insertDBAction(id.toString(), starred = true) true @@ -245,7 +257,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap suspend fun markAllAsRead(items: ArrayList): Boolean { var success = false - if (isNetworkAvailable() && api.markAllAsRead(items.map { it.id.toString() })?.isSuccess == true) { + if (isNetworkAvailable() && api.markAllAsRead(items.map { it.id.toString() })?.isSuccess) { success = true for (item in items) { markAsReadLocally(item) @@ -334,7 +346,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap suspend fun updateRemote(): Boolean { return if (isNetworkAvailable()) { - api.update()?.equals("finished") ?: false + api.update()?.equals("finished") } else { false } @@ -367,8 +379,8 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap if (isNetworkAvailable()) { val fetchedVersion = api.version() - if (fetchedVersion != null && fetchedVersion.getApiMajorVersion() != apiMajorVersion) { - appSettingsService.updateApiVersion(fetchedVersion.getApiMajorVersion()) + if (fetchedVersion.success && fetchedVersion.data != null && fetchedVersion.data.getApiMajorVersion() != apiMajorVersion) { + appSettingsService.updateApiVersion(fetchedVersion.data.getApiMajorVersion()) } } } @@ -429,7 +441,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap val newItems = getMaxItemsForBackground(ItemType.UNREAD) val allItems = getMaxItemsForBackground(ItemType.ALL) val starredItems = getMaxItemsForBackground(ItemType.STARRED) - insertDBItems(newItems.orEmpty() + allItems.orEmpty() + starredItems.orEmpty()) + insertDBItems(newItems + allItems + starredItems) return newItems } catch (e: Throwable) { // We do nothing diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt index c094532..c9f5c9a 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt @@ -10,6 +10,7 @@ import io.ktor.client.plugins.contentnegotiation.* import io.ktor.client.plugins.logging.* import io.ktor.client.request.* import io.ktor.client.request.forms.* +import io.ktor.client.statement.* import io.ktor.http.* import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.json.Json @@ -34,7 +35,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { appSettingsService.logApiCalls(message) } } - level = LogLevel.ALL + level = LogLevel.INFO } install(HttpTimeout) { requestTimeoutMillis = appSettingsService.getApiTimeout() @@ -65,11 +66,11 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { client = createHttpClient() } - suspend fun login(): SelfossModel.SuccessResponse? = - client.get(url("/login")) { + suspend fun login(): SelfossModel.SuccessResponse = + maybeResponse(client.get(url("/login")) { parameter("username", appSettingsService.getUserName()) parameter("password", appSettingsService.getPassword()) - }.body() + }) suspend fun getItems( type: String, @@ -79,8 +80,8 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { search: String?, updatedSince: String?, items: Int? = null - ): List? = - client.get(url("/items")) { + ): SelfossModel.StatusAndData> = + bodyOrFailure(client.get(url("/items")) { parameter("username", appSettingsService.getUserName()) parameter("password", appSettingsService.getPassword()) parameter("type", type) @@ -90,74 +91,74 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { parameter("updatedsince", updatedSince) parameter("items", items ?: appSettingsService.getItemsNumber()) parameter("offset", offset) - }.body() + }) - suspend fun stats(): SelfossModel.Stats? = - client.get(url("/stats")) { + suspend fun stats(): SelfossModel.StatusAndData = + bodyOrFailure(client.get(url("/stats")) { parameter("username", appSettingsService.getUserName()) parameter("password", appSettingsService.getPassword()) - }.body() + }) - suspend fun tags(): List? = - client.get(url("/tags")) { + suspend fun tags(): SelfossModel.StatusAndData> = + bodyOrFailure(client.get(url("/tags")) { parameter("username", appSettingsService.getUserName()) parameter("password", appSettingsService.getPassword()) - }.body() + }) - suspend fun update(): String? = - client.get(url("/update")) { + suspend fun update(): SelfossModel.StatusAndData = + bodyOrFailure(client.get(url("/update")) { parameter("username", appSettingsService.getUserName()) parameter("password", appSettingsService.getPassword()) - }.body() + }) - suspend fun spouts(): Map? = - client.get(url("/sources/spouts")) { + suspend fun spouts(): SelfossModel.StatusAndData> = + bodyOrFailure(client.get(url("/sources/spouts")) { parameter("username", appSettingsService.getUserName()) parameter("password", appSettingsService.getPassword()) - }.body() + }) - suspend fun sources(): ArrayList? = - client.get(url("/sources/list")) { + suspend fun sources(): SelfossModel.StatusAndData> = + bodyOrFailure(client.get(url("/sources/list")) { parameter("username", appSettingsService.getUserName()) parameter("password", appSettingsService.getPassword()) - }.body() + }) - suspend fun version(): SelfossModel.ApiVersion? = - client.get(url("/api/about")).body() + suspend fun version(): SelfossModel.StatusAndData = + bodyOrFailure(client.get(url("/api/about"))) - suspend fun markAsRead(id: String): SelfossModel.SuccessResponse? = - client.post(url("/mark/$id")) { + suspend fun markAsRead(id: String): SelfossModel.SuccessResponse = + maybeResponse(client.post(url("/mark/$id")) { parameter("username", appSettingsService.getUserName()) parameter("password", appSettingsService.getPassword()) - }.body() + }) - suspend fun unmarkAsRead(id: String): SelfossModel.SuccessResponse? = - client.post(url("/unmark/$id")) { + suspend fun unmarkAsRead(id: String): SelfossModel.SuccessResponse = + maybeResponse(client.post(url("/unmark/$id")) { parameter("username", appSettingsService.getUserName()) parameter("password", appSettingsService.getPassword()) - }.body() + }) - suspend fun starr(id: String): SelfossModel.SuccessResponse? = - client.post(url("/starr/$id")) { + suspend fun starr(id: String): SelfossModel.SuccessResponse = + maybeResponse(client.post(url("/starr/$id")) { parameter("username", appSettingsService.getUserName()) parameter("password", appSettingsService.getPassword()) - }.body() + }) - suspend fun unstarr(id: String): SelfossModel.SuccessResponse? = - client.post(url("/unstarr/$id")) { + suspend fun unstarr(id: String): SelfossModel.SuccessResponse = + maybeResponse(client.post(url("/unstarr/$id")) { parameter("username", appSettingsService.getUserName()) parameter("password", appSettingsService.getPassword()) - }.body() + }) - suspend fun markAllAsRead(ids: List): SelfossModel.SuccessResponse? = - client.submitForm( + suspend fun markAllAsRead(ids: List): SelfossModel.SuccessResponse = + maybeResponse(client.submitForm( url = url("/mark"), formParameters = Parameters.build { append("username", appSettingsService.getUserName()) append("password", appSettingsService.getPassword()) ids.map { append("ids[]", it) } } - ).body() + )) suspend fun createSourceForVersion( title: String, @@ -166,12 +167,14 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { tags: String, filter: String, version: Int - ): SelfossModel.SuccessResponse? = - if (version > 1) { - createSource2(title, url, spout, tags, filter) - } else { - createSource(title, url, spout, tags, filter) - } + ): SelfossModel.SuccessResponse = + maybeResponse( + if (version > 1) { + createSource2(title, url, spout, tags, filter) + } else { + createSource(title, url, spout, tags, filter) + } + ) suspend fun createSource( title: String, @@ -179,7 +182,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { spout: String, tags: String, filter: String - ): SelfossModel.SuccessResponse? = + ): HttpResponse = client.submitForm( url = url("/source?username=${appSettingsService.getUserName()}&password=${appSettingsService.getPassword()}"), formParameters = Parameters.build { @@ -189,7 +192,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { append("tags", tags) append("filter", filter) } - ).body() + ) suspend fun createSource2( title: String, @@ -197,7 +200,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { spout: String, tags: String, filter: String - ): SelfossModel.SuccessResponse? = + ): HttpResponse = client.submitForm( url = url("/source?username=${appSettingsService.getUserName()}&password=${appSettingsService.getPassword()}"), formParameters = Parameters.build { @@ -207,11 +210,27 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { append("tags[]", tags) append("filter", filter) } - ).body() + ) - suspend fun deleteSource(id: Int): SelfossModel.SuccessResponse? = - client.delete(url("/source/$id")) { + suspend fun deleteSource(id: Int): SelfossModel.SuccessResponse = + maybeResponse(client.delete(url("/source/$id")) { parameter("username", appSettingsService.getUserName()) parameter("password", appSettingsService.getPassword()) - }.body() + }) + + suspend fun maybeResponse(r: HttpResponse): SelfossModel.SuccessResponse { + return if (r.status.isSuccess()) { + r.body() + } else { + SelfossModel.SuccessResponse(false) + } + } + + suspend inline fun bodyOrFailure(r: HttpResponse): SelfossModel.StatusAndData { + return if (r.status.isSuccess()) { + SelfossModel.StatusAndData.succes(r.body()) + } else { + SelfossModel.StatusAndData.error() + } + } } \ No newline at end of file diff --git a/version.sh b/version.sh new file mode 100755 index 0000000..afb679a --- /dev/null +++ b/version.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +echo "Creating tag $1" + +TAG="v$1" +git tag -a ${TAG} -m ${TAG} + +if [[ "$@" == *'--from-ci'* ]] +then + echo "Tag created. HANDLE IN CI" +else + echo "Pushing tag" + git push origin ${TAG} +fi