Compare commits

...

37 Commits

Author SHA1 Message Date
417a33eb25 Merge pull request 'Running migrations.' (#118) from fix-migration into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/118
2022-12-28 14:49:11 +00:00
2e7f7f23b3 No duplicate builds for PRs. 2022-12-28 15:34:22 +01:00
e5e182761e Running migrations. 2022-12-28 15:27:17 +01:00
a094d88799 Merge pull request 'Make the author field nullable' (#117) from davidoskky/ReaderForSelfoss-multiplatform:author into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/117
2022-12-28 14:25:36 +00:00
e51915d1cd Include author field when updating the database 2022-12-28 14:25:56 +01:00
3a654f6ede Migrate the database table 2022-12-28 14:25:34 +01:00
5227751dca Make the author field nullable 2022-12-28 11:02:43 +01:00
27eafe4ff4 Delete sources from DB and reload items on source deletion. 2022-12-26 22:27:28 +01:00
8c83a9408b Drone should work better. 2022-12-26 22:26:28 +01:00
fe2410f719 Handling author field. 2022-12-26 21:49:55 +01:00
a5e86bfb77 Date format issues. 2022-12-26 15:02:19 +01:00
23be633798 Add api version to the reports. 2022-12-25 22:45:12 +01:00
813e0707d8 Date format issue. 2022-12-25 22:41:34 +01:00
9ed9bf07fc Items in repository. 2022-12-23 22:53:16 +01:00
47265c10d0 Trying nexus build. 2022-12-23 14:59:58 +01:00
5cc633246a Debugging images issues. 2022-12-22 20:28:49 +01:00
1f40385786 Context should not be null, but handle the case for now. 2022-12-19 22:08:28 +01:00
eb2876324a This seems to be needed. 2022-12-19 20:47:04 +01:00
633b817d76 Remonving matomo. 2022-12-18 21:07:42 +01:00
2cfaa9b285 Logout fix. 2022-12-18 20:42:13 +01:00
f42ae97326 Explicitly failing for non selfoss rss files. 2022-12-18 20:41:17 +01:00
3b0028164b Glide update + trying requests. 2022-12-17 22:25:23 +01:00
7420adeb5c Do not ignore git version. 2022-12-14 21:42:56 +01:00
316027ca3b Tag for build. 2022-12-14 21:26:33 +01:00
9d58fba5c9 Cleaning. 2022-12-14 21:07:03 +01:00
284c19ef89 More cleaning. 2022-12-14 20:54:48 +01:00
7cfd17231a Cleaning. 2022-12-13 22:22:31 +01:00
527830a5ae Merge branch 'sonar-qube' 2022-12-13 21:53:10 +01:00
c4ed30f594 Fixes #112. 2022-12-13 21:32:48 +01:00
156c1681cf Fixes #111. 2022-12-13 21:19:05 +01:00
3593fbca78 Sonar scanner. 2022-12-13 21:11:38 +01:00
430fc8e8cb Fixes #110. 2022-12-13 20:40:50 +01:00
4fce19bad4 Trying to set code coverage. 2022-12-13 20:29:51 +01:00
49f5848e7b Merge pull request 'Fixes #108.' (#109) from bug/lateinit into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/109
2022-12-12 20:15:44 +00:00
90452100a4 Fixes #108. 2022-12-12 21:11:26 +01:00
bf1196dd0f Translations. 2022-12-09 20:27:38 +01:00
4316dc6516 Removing hidden tags. 2022-12-09 20:23:01 +01:00
57 changed files with 647 additions and 1077 deletions

View File

@ -3,31 +3,38 @@ type: docker
name: test name: test
steps: steps:
- name: AnylyseBuildTest - 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\npushCache=false\nsystemProp.org.gradle.internal.http.connectionTimeout=180000\nsystemProp.org.gradle.internal.http.socketTimeout=180000" >> ~/.gradle/gradle.properties
- echo "---------------------------------------------------------" - echo "---------------------------------------------------------"
- echo "Analysing..."
- ./gradlew sonarqube -Dsonar.projectKey=RFS2 -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN
- echo "---------------------------------------------------------"
- echo "Building..." - echo "Building..."
- ./gradlew build - ./gradlew build -x test
- echo "---------------------------------------------------------" - echo "---------------------------------------------------------"
- echo "Testing..." - echo "Testing..."
- echo "---------------------------------------------------------" - echo "---------------------------------------------------------"
- ./gradlew test - ./gradlew koverMergedXmlReport
environment: environment:
TZ: Europe/Paris
SONAR_HOST_URL: SONAR_HOST_URL:
from_secret: sonarScannerHostUrl from_secret: sonarScannerHostUrl
SONAR_LOGIN: SONAR_LOGIN:
from_secret: sonarScannerLogin 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
- pull_request
--- ---
kind: pipeline kind: pipeline
@ -43,6 +50,7 @@ steps:
- git remote add pushing https://$GITEA_USR:$GITEA_PASS@gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform.git - git remote add pushing https://$GITEA_USR:$GITEA_PASS@gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform.git
- git push pushing --tags - git push pushing --tags
environment: environment:
TZ: Europe/Paris
GITEA_USR: GITEA_USR:
from_secret: giteaUsr from_secret: giteaUsr
GITEA_PASS: GITEA_PASS:
@ -68,10 +76,7 @@ steps:
from_secret: privateKey from_secret: privateKey
command_timeout: 2m command_timeout: 2m
script: script:
- cd /home/ubuntu - 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/
- sudo rm -rf /var/www/amine/version.txt
- sudo chown www-data:www-data ./version.txt
- sudo mv version.txt /var/www/amine/
trigger: trigger:
event: event:
@ -88,9 +93,12 @@ steps:
- name: build - name: build
image: mingc/android-build-box:latest image: mingc/android-build-box:latest
commands: commands:
- echo "---------------------------------------------------------"
- echo "Fetch tags..."
- git fetch --tags
- 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=false\npushCache=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 -P pushCache=false
@ -107,6 +115,7 @@ steps:
- echo "Verify" - echo "Verify"
- $ANDROID_HOME/build-tools/31.0.0/apksigner verify signed.apk - $ANDROID_HOME/build-tools/31.0.0/apksigner verify signed.apk
environment: environment:
TZ: Europe/Paris
YOUR_KEYSTORE_PASSWORD: YOUR_KEYSTORE_PASSWORD:
from_secret: keyPass from_secret: keyPass
YOUR_KEY_ALIAS: YOUR_KEY_ALIAS:

View File

@ -8,6 +8,7 @@ plugins {
kotlin("android") kotlin("android")
kotlin("kapt") kotlin("kapt")
id("com.mikepenz.aboutlibraries.plugin") id("com.mikepenz.aboutlibraries.plugin")
id("org.jetbrains.kotlinx.kover") version "0.6.1"
} }
fun Project.execWithOutput(cmd: String, ignore: Boolean = false): String { fun Project.execWithOutput(cmd: String, ignore: Boolean = false): String {
@ -145,8 +146,8 @@ dependencies {
implementation("com.amulyakhare:com.amulyakhare.textdrawable:1.0.1") implementation("com.amulyakhare:com.amulyakhare.textdrawable:1.0.1")
// glide // glide
kapt("com.github.bumptech.glide:compiler:4.11.0") kapt("com.github.bumptech.glide:compiler:4.14.2")
implementation("com.github.bumptech.glide:okhttp3-integration:4.1.1") implementation("com.github.bumptech.glide:okhttp3-integration:4.14.2")
// Themes // Themes
implementation("com.github.rubensousa:floatingtoolbar:1.5.1") implementation("com.github.rubensousa:floatingtoolbar:1.5.1")
@ -187,9 +188,6 @@ dependencies {
implementation("ch.acra:acra-http:$acraVersion") implementation("ch.acra:acra-http:$acraVersion")
implementation("ch.acra:acra-toast:$acraVersion") implementation("ch.acra:acra-toast:$acraVersion")
// Matomo
implementation("com.github.matomo-org:matomo-sdk-android:4.1.4")
} }
tasks.withType<Test> { tasks.withType<Test> {

View File

@ -4,6 +4,6 @@ import org.acra.ACRA
import org.acra.ktx.sendSilentlyWithAcra import org.acra.ktx.sendSilentlyWithAcra
fun Throwable.sendSilentlyWithAcraWithName(name: String) { fun Throwable.sendSilentlyWithAcraWithName(name: String) {
ACRA.errorReporter.putCustomData("error_source", name) ACRA.errorReporter.putCustomData("error_source", name)
this.sendSilentlyWithAcra() this.sendSilentlyWithAcra()
} }

View File

@ -1,7 +1,6 @@
package bou.amine.apps.readerforselfossv2.android package bou.amine.apps.readerforselfossv2.android
import android.content.Intent import android.content.Intent
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
@ -31,7 +30,6 @@ import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.repository.Repository import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.service.AppSettingsService import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import bou.amine.apps.readerforselfossv2.utils.ItemType import bou.amine.apps.readerforselfossv2.utils.ItemType
import bou.amine.apps.readerforselfossv2.utils.longHash
import com.ashokvarma.bottomnavigation.BottomNavigationBar import com.ashokvarma.bottomnavigation.BottomNavigationBar
import com.ashokvarma.bottomnavigation.BottomNavigationItem import com.ashokvarma.bottomnavigation.BottomNavigationItem
import com.ashokvarma.bottomnavigation.TextBadgeItem import com.ashokvarma.bottomnavigation.TextBadgeItem
@ -41,8 +39,6 @@ import kotlinx.coroutines.launch
import org.kodein.di.DIAware import org.kodein.di.DIAware
import org.kodein.di.android.closestDI import org.kodein.di.android.closestDI
import org.kodein.di.instance import org.kodein.di.instance
import org.matomo.sdk.Tracker
import org.matomo.sdk.extra.TrackHelper
import java.security.MessageDigest import java.security.MessageDigest
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -66,8 +62,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
private var fromTabShortcut: Boolean = false private var fromTabShortcut: Boolean = false
private lateinit var tagsBadge: Map<Long, Int>
private val settingsLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { private val settingsLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
appSettingsService.refreshUserSettings() appSettingsService.refreshUserSettings()
} }
@ -75,7 +69,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
override val di by closestDI() override val di by closestDI()
private val repository : Repository by instance() private val repository : Repository by instance()
private val appSettingsService : AppSettingsService by instance() private val appSettingsService : AppSettingsService by instance()
private val tracker : Tracker by instance()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -83,8 +76,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
binding = ActivityHomeBinding.inflate(layoutInflater) binding = ActivityHomeBinding.inflate(layoutInflater)
val view = binding.root val view = binding.root
TrackHelper.track().screen("/home").with(tracker)
fromTabShortcut = intent.getIntExtra("shortcutTab", -1) != -1 fromTabShortcut = intent.getIntExtra("shortcutTab", -1) != -1
repository.offlineOverride = intent.getBooleanExtra("startOffline", false) repository.offlineOverride = intent.getBooleanExtra("startOffline", false)
@ -156,15 +147,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
adapter.handleItemAtIndex(position) adapter.handleItemAtIndex(position)
val tagHashes = i.tags.map { it.longHash() }
tagsBadge = tagsBadge.map {
if (tagHashes.contains(it.key)) {
(it.key to (it.value - 1))
} else {
(it.key to it.value)
}
}.toMap()
// Just load everythin // Just load everythin
if (items.size <= 0) { if (items.size <= 0) {
getElementsAccordingToTab() getElementsAccordingToTab()
@ -612,9 +594,9 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
repository.logout() repository.logout()
} }
this@HomeActivity.finish()
val intent = Intent(this, LoginActivity::class.java) val intent = Intent(this, LoginActivity::class.java)
this.startActivity(intent) this.startActivity(intent)
this@HomeActivity.finish()
return true return true
} }
R.id.action_settings -> { R.id.action_settings -> {

View File

@ -11,6 +11,7 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.widget.TextView import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
@ -22,13 +23,10 @@ import com.mikepenz.aboutlibraries.LibsBuilder
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 org.acra.ACRA
import org.kodein.di.DIAware import org.kodein.di.DIAware
import org.kodein.di.android.closestDI import org.kodein.di.android.closestDI
import org.kodein.di.instance import org.kodein.di.instance
import org.matomo.sdk.Tracker
import org.matomo.sdk.extra.DimensionQueue
import org.matomo.sdk.extra.DownloadTracker
import org.matomo.sdk.extra.TrackHelper
import java.security.MessageDigest import java.security.MessageDigest
@ -40,18 +38,13 @@ class LoginActivity : AppCompatActivity(), DIAware {
private lateinit var binding: ActivityLoginBinding private lateinit var binding: ActivityLoginBinding
override val di by closestDI() override val di by closestDI()
private val repository : Repository by instance() private val repository: Repository by instance()
private val appSettingsService : AppSettingsService by instance() private val appSettingsService: AppSettingsService by instance()
private val tracker : Tracker by instance()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
TrackHelper.track().download().identifier(DownloadTracker.Extra.ApkChecksum(applicationContext))
.with(tracker)
TrackHelper.track().screen("/login").with(tracker)
handleTheme() handleTheme()
binding = ActivityLoginBinding.inflate(layoutInflater) binding = ActivityLoginBinding.inflate(layoutInflater)
@ -64,7 +57,30 @@ class LoginActivity : AppCompatActivity(), DIAware {
handleBaseUrlFail() handleBaseUrlFail()
if (appSettingsService.getBaseUrl().isNotEmpty()) { if (appSettingsService.getBaseUrl().isNotEmpty()) {
goToMain() showProgress(true)
// This should be reverted when "old" users connected with a non-selfoss rss
// are handled. Revert to "simple" way.
CoroutineScope(Dispatchers.Main).launch {
try {
val (errorFetching, displaySelfossOnly) = repository.shouldBeSelfossInstance()
if (!errorFetching && !displaySelfossOnly) {
goToMain()
} else {
showProgress(false)
if (displaySelfossOnly) {
Toast.makeText(
applicationContext,
R.string.application_selfoss_only,
Toast.LENGTH_LONG
).show()
}
repository.logout()
}
} catch (e: Throwable) {
repository.logout()
showProgress(false)
}
}
} }
handleActions() handleActions()
@ -114,15 +130,7 @@ class LoginActivity : AppCompatActivity(), DIAware {
private fun goToMain() { private fun goToMain() {
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
repository.updateApiVersion() repository.updateApiVersion()
ACRA.errorReporter.putCustomData("SELFOSS_API_VERSION", appSettingsService.getApiVersion().toString())
val messageDigest: MessageDigest = MessageDigest.getInstance("SHA-256")
messageDigest.update(appSettingsService.getBaseUrl().toByteArray())
tracker.userId = String(messageDigest.digest())
val mDimensionQueue = DimensionQueue(tracker)
mDimensionQueue.add(1, appSettingsService.getApiVersion().toString())
tracker.isOptOut = !appSettingsService.isAnalyticsEnabled()
} }
val intent = Intent(this, HomeActivity::class.java) val intent = Intent(this, HomeActivity::class.java)
startActivity(intent) startActivity(intent)
@ -191,17 +199,27 @@ class LoginActivity : AppCompatActivity(), DIAware {
repository.refreshLoginInformation(url, login, password) repository.refreshLoginInformation(url, login, password)
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.Main).launch {
val result = repository.login() val result = repository.login()
if (result) { if (result) {
goToMain() val (errorFetching, displaySelfossOnly) = repository.shouldBeSelfossInstance()
} else { if (!errorFetching && !displaySelfossOnly) {
CoroutineScope(Dispatchers.Main).launch { goToMain()
} else {
if (displaySelfossOnly) {
Toast.makeText(
applicationContext,
R.string.application_selfoss_only,
Toast.LENGTH_LONG
).show()
}
preferenceError() preferenceError()
} }
} else {
preferenceError()
} }
showProgress(false)
} }
showProgress(false)
} }
} }
@ -215,11 +233,11 @@ class LoginActivity : AppCompatActivity(), DIAware {
.alpha( .alpha(
if (show) 0F else 1F if (show) 0F else 1F
).setListener(object : AnimatorListenerAdapter() { ).setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) { override fun onAnimationEnd(animation: Animator) {
binding.loginForm.visibility = if (show) View.GONE else View.VISIBLE binding.loginForm.visibility = if (show) View.GONE else View.VISIBLE
}
} }
} )
)
binding.loginProgress.visibility = if (show) View.VISIBLE else View.GONE binding.loginProgress.visibility = if (show) View.VISIBLE else View.GONE
binding.loginProgress binding.loginProgress
@ -228,11 +246,11 @@ class LoginActivity : AppCompatActivity(), DIAware {
.alpha( .alpha(
if (show) 1F else 0F if (show) 1F else 0F
).setListener(object : AnimatorListenerAdapter() { ).setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) { override fun onAnimationEnd(animation: Animator) {
binding.loginProgress.visibility = if (show) View.VISIBLE else View.GONE binding.loginProgress.visibility = if (show) View.VISIBLE else View.GONE
}
} }
} )
)
} }
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {

View File

@ -3,10 +3,7 @@ package bou.amine.apps.readerforselfossv2.android
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.content.Context import android.content.Context
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Build import android.os.Build
import android.widget.ImageView
import android.widget.Toast import android.widget.Toast
import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
@ -18,8 +15,6 @@ import bou.amine.apps.readerforselfossv2.dao.DriverFactory
import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB
import bou.amine.apps.readerforselfossv2.repository.Repository import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.service.AppSettingsService import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import com.github.ln_12.library.ConnectivityStatus import com.github.ln_12.library.ConnectivityStatus
import io.github.aakira.napier.DebugAntilog import io.github.aakira.napier.DebugAntilog
import io.github.aakira.napier.Napier import io.github.aakira.napier.Napier
@ -35,9 +30,6 @@ import org.acra.data.StringFormat
import org.acra.ktx.initAcra import org.acra.ktx.initAcra
import org.acra.sender.HttpSender import org.acra.sender.HttpSender
import org.kodein.di.* import org.kodein.di.*
import org.matomo.sdk.Matomo
import org.matomo.sdk.Tracker
import org.matomo.sdk.TrackerBuilder
class MyApp : MultiDexApplication(), DIAware { class MyApp : MultiDexApplication(), DIAware {
@ -48,8 +40,6 @@ class MyApp : MultiDexApplication(), DIAware {
bind<Repository>() with singleton { Repository(instance(), instance(), isConnectionAvailable, instance()) } bind<Repository>() with singleton { Repository(instance(), instance(), isConnectionAvailable, instance()) }
bind<ConnectivityStatus>() with singleton { ConnectivityStatus(applicationContext) } bind<ConnectivityStatus>() with singleton { ConnectivityStatus(applicationContext) }
bind<AppViewModel>() with singleton { AppViewModel(repository = instance()) } bind<AppViewModel>() with singleton { AppViewModel(repository = instance()) }
bind<Tracker>() with singleton { TrackerBuilder.createDefault("https://matomo.amine-louveau.fr/matomo.php", if (BuildConfig.DEBUG) 4 else 5).build(
Matomo.getInstance(applicationContext)) }
} }
private val repository: Repository by instance() private val repository: Repository by instance()
@ -88,6 +78,8 @@ class MyApp : MultiDexApplication(), DIAware {
} }
} }
} }
repository.migrate(driverFactory)
} }
override fun attachBaseContext(base: Context?) { override fun attachBaseContext(base: Context?) {

View File

@ -30,6 +30,8 @@ class ReaderActivity : AppCompatActivity(), DIAware {
private lateinit var binding: ActivityReaderBinding private lateinit var binding: ActivityReaderBinding
private var allItems: ArrayList<SelfossModel.Item> = ArrayList()
override val di by closestDI() override val di by closestDI()
private val repository: Repository by instance() private val repository: Repository by instance()
private val appSettingsService: AppSettingsService by instance() private val appSettingsService: AppSettingsService by instance()
@ -61,12 +63,14 @@ class ReaderActivity : AppCompatActivity(), DIAware {
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true) supportActionBar?.setDisplayShowHomeEnabled(true)
if (allItems.isEmpty()) { currentItem = intent.getIntExtra("currentItem", 0)
allItems = repository.getReaderItems()
if (allItems.isEmpty() || currentItem > allItems.size) {
finish() finish()
} }
currentItem = intent.getIntExtra("currentItem", 0)
readItem(allItems[currentItem]) readItem(allItems[currentItem])
binding.pager.adapter = ScreenSlidePagerAdapter(this) binding.pager.adapter = ScreenSlidePagerAdapter(this)
@ -214,8 +218,4 @@ class ReaderActivity : AppCompatActivity(), DIAware {
startActivity(intent) startActivity(intent)
overridePendingTransition(0, 0) overridePendingTransition(0, 0)
} }
companion object {
var allItems: ArrayList<SelfossModel.Item> = ArrayList()
}
} }

View File

@ -62,7 +62,7 @@ class ItemCardAdapter(
binding.title.setLinkTextColor(c.resources.getColor(R.color.colorAccent)) binding.title.setLinkTextColor(c.resources.getColor(R.color.colorAccent))
binding.sourceTitleAndDate.text = itm.sourceAndDateText() binding.sourceTitleAndDate.text = itm.sourceAuthorAndDate()
if (!appSettingsService.isFullHeightCardsEnabled()) { if (!appSettingsService.isFullHeightCardsEnabled()) {
binding.itemImage.maxHeight = imageMaxHeight binding.itemImage.maxHeight = imageMaxHeight
@ -132,8 +132,8 @@ class ItemCardAdapter(
private fun handleLinkOpening() { private fun handleLinkOpening() {
binding.root.setOnClickListener { binding.root.setOnClickListener {
repository.setReaderItems(items)
c.openItemUrl( c.openItemUrl(
items,
bindingAdapterPosition, bindingAdapterPosition,
items[bindingAdapterPosition].getLinkDecoded(), items[bindingAdapterPosition].getLinkDecoded(),
appSettingsService.isArticleViewerEnabled(), appSettingsService.isArticleViewerEnabled(),

View File

@ -51,7 +51,7 @@ class ItemListAdapter(
binding.title.setLinkTextColor(c.resources.getColor(R.color.colorAccent)) binding.title.setLinkTextColor(c.resources.getColor(R.color.colorAccent))
binding.sourceTitleAndDate.text = itm.sourceAndDateText() binding.sourceTitleAndDate.text = itm.sourceAuthorAndDate()
if (itm.getThumbnail(repository.baseUrl).isEmpty()) { if (itm.getThumbnail(repository.baseUrl).isEmpty()) {
@ -84,8 +84,8 @@ class ItemListAdapter(
private fun handleLinkOpening() { private fun handleLinkOpening() {
binding.root.setOnClickListener { binding.root.setOnClickListener {
repository.setReaderItems(items)
c.openItemUrl( c.openItemUrl(
items,
bindingAdapterPosition, bindingAdapterPosition,
items[bindingAdapterPosition].getLinkDecoded(), items[bindingAdapterPosition].getLinkDecoded(),
appSettingsService.isArticleViewerEnabled(), appSettingsService.isArticleViewerEnabled(),

View File

@ -36,9 +36,7 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
Snackbar.LENGTH_LONG Snackbar.LENGTH_LONG
) )
.setAction(R.string.undo_string) { .setAction(R.string.undo_string) {
CoroutineScope(Dispatchers.IO).launch { unreadItemAtIndex(item, position, false)
unreadItemAtIndex(item, position, false)
}
} }
val view = s.view val view = s.view

View File

@ -78,9 +78,9 @@ class SourcesListAdapter(
val deleteBtn: Button = mView.findViewById(R.id.deleteBtn) val deleteBtn: Button = mView.findViewById(R.id.deleteBtn)
deleteBtn.setOnClickListener { deleteBtn.setOnClickListener {
val (id) = items[bindingAdapterPosition] val (id, title) = items[bindingAdapterPosition]
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
val successfullyDeletedSource = repository.deleteSource(id) val successfullyDeletedSource = repository.deleteSource(id, title)
if (successfullyDeletedSource) { if (successfullyDeletedSource) {
items.removeAt(bindingAdapterPosition) items.removeAt(bindingAdapterPosition)
notifyItemRemoved(bindingAdapterPosition) notifyItemRemoved(bindingAdapterPosition)

View File

@ -16,7 +16,6 @@ import android.webkit.WebView
import android.webkit.WebViewClient import android.webkit.WebViewClient
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.content.res.ResourcesCompat
import androidx.core.widget.NestedScrollView import androidx.core.widget.NestedScrollView
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import bou.amine.apps.readerforselfossv2.android.ImageActivity import bou.amine.apps.readerforselfossv2.android.ImageActivity
@ -45,8 +44,6 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton
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 org.acra.ktx.sendSilentlyWithAcra
import org.acra.ktx.sendWithAcra
import org.kodein.di.DI import org.kodein.di.DI
import org.kodein.di.DIAware import org.kodein.di.DIAware
import org.kodein.di.android.x.closestDI import org.kodein.di.android.x.closestDI
@ -70,7 +67,7 @@ class ArticleFragment : Fragment(), DIAware {
private lateinit var fab: FloatingActionButton private lateinit var fab: FloatingActionButton
private lateinit var textAlignment: String private lateinit var textAlignment: String
private var _binding: FragmentArticleBinding? = null private var _binding: FragmentArticleBinding? = null
private val binding get() = _binding!! private val binding get() = _binding
override val di : DI by closestDI() override val di : DI by closestDI()
private val repository: Repository by instance() private val repository: Repository by instance()
@ -104,7 +101,7 @@ class ArticleFragment : Fragment(), DIAware {
contentText = item.content contentText = item.content
contentTitle = item.title.getHtmlDecoded() contentTitle = item.title.getHtmlDecoded()
contentImage = item.getThumbnail(repository.baseUrl) contentImage = item.getThumbnail(repository.baseUrl)
contentSource = item.sourceAndDateText() contentSource = item.sourceAuthorAndDate()
allImages = item.getImages() allImages = item.getImages()
fontSize = appSettingsService.getFontSize() fontSize = appSettingsService.getFontSize()
@ -113,13 +110,13 @@ class ArticleFragment : Fragment(), DIAware {
refreshAlignment() refreshAlignment()
fab = binding.fab fab = binding!!.fab
fab.backgroundTintList = ColorStateList.valueOf(resources.getColor(R.color.colorAccent)) fab.backgroundTintList = ColorStateList.valueOf(resources.getColor(R.color.colorAccent))
fab.rippleColor = resources.getColor(R.color.colorAccentDark) fab.rippleColor = resources.getColor(R.color.colorAccentDark)
val floatingToolbar: FloatingToolbar = binding.floatingToolbar val floatingToolbar: FloatingToolbar = binding!!.floatingToolbar
floatingToolbar.attachFab(fab) floatingToolbar.attachFab(fab)
floatingToolbar.background = ColorDrawable(resources.getColor(R.color.colorAccent)) floatingToolbar.background = ColorDrawable(resources.getColor(R.color.colorAccent))
@ -167,35 +164,35 @@ class ArticleFragment : Fragment(), DIAware {
floatingToolbar.show() floatingToolbar.show()
} }
binding.source.text = contentSource binding!!.source.text = contentSource
if (typeface != null) { if (typeface != null) {
binding.source.typeface = typeface binding!!.source.typeface = typeface
} }
if (contentText.isEmptyOrNullOrNullString()) { if (contentText.isEmptyOrNullOrNullString()) {
getContentFromMercury() getContentFromMercury()
} else { } else {
binding.titleView.text = contentTitle binding!!.titleView.text = contentTitle
if (typeface != null) { if (typeface != null) {
binding.titleView.typeface = typeface binding!!.titleView.typeface = typeface
} }
htmlToWebview() htmlToWebview()
if (!contentImage.isEmptyOrNullOrNullString() && context != null) { if (!contentImage.isEmptyOrNullOrNullString() && context != null) {
binding.imageView.visibility = View.VISIBLE binding!!.imageView.visibility = View.VISIBLE
Glide Glide
.with(requireContext()) .with(requireContext())
.asBitmap() .asBitmap()
.load(contentImage) .load(contentImage)
.apply(RequestOptions.fitCenterTransform()) .apply(RequestOptions.fitCenterTransform())
.into(binding.imageView) .into(binding!!.imageView)
} else { } else {
binding.imageView.visibility = View.GONE binding!!.imageView.visibility = View.GONE
} }
} }
binding.nestedScrollView.setOnScrollChangeListener( binding!!.nestedScrollView.setOnScrollChangeListener(
NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY -> NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
if (scrollY > oldScrollY) { if (scrollY > oldScrollY) {
floatingToolbar.hide() floatingToolbar.hide()
@ -224,7 +221,7 @@ class ArticleFragment : Fragment(), DIAware {
.show() .show()
} }
return binding.root return binding!!.root
} }
override fun onDestroyView() { override fun onDestroyView() {
@ -242,16 +239,16 @@ class ArticleFragment : Fragment(), DIAware {
private fun getContentFromMercury() { private fun getContentFromMercury() {
if (repository.isNetworkAvailable()) { if (repository.isNetworkAvailable()) {
binding.progressBar.visibility = View.VISIBLE binding!!.progressBar.visibility = View.VISIBLE
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
try { try {
val response = mercuryApi.query(url) val response = mercuryApi.query(url)
if (response.success && response.data != null && !response.data?.content.isNullOrEmpty()) { if (response.success && response.data != null && !response.data?.content.isNullOrEmpty()) {
binding.titleView.text = response.data!!.title.orEmpty() binding!!.titleView.text = response.data!!.title.orEmpty()
try { try {
if (typeface != null) { if (typeface != null) {
binding.titleView.typeface = typeface binding!!.titleView.typeface = typeface
} }
} catch (e: Exception) { } catch (e: Exception) {
e.sendSilentlyWithAcraWithName("getContentFromMercury > typeface") e.sendSilentlyWithAcraWithName("getContentFromMercury > typeface")
@ -275,7 +272,7 @@ class ArticleFragment : Fragment(), DIAware {
if (!response.data?.lead_image_url.isNullOrEmpty() && context != null) { if (!response.data?.lead_image_url.isNullOrEmpty() && context != null) {
try { try {
binding.imageView.visibility = View.VISIBLE binding!!.imageView.visibility = View.VISIBLE
try { try {
Glide Glide
.with(requireContext()) .with(requireContext())
@ -284,7 +281,7 @@ class ArticleFragment : Fragment(), DIAware {
response.data!!.lead_image_url.orEmpty() response.data!!.lead_image_url.orEmpty()
) )
.apply(RequestOptions.fitCenterTransform()) .apply(RequestOptions.fitCenterTransform())
.into(binding.imageView) .into(binding!!.imageView)
} catch (e: Exception) { } catch (e: Exception) {
e.sendSilentlyWithAcraWithName("getContentFromMercury > glide lead image") e.sendSilentlyWithAcraWithName("getContentFromMercury > glide lead image")
} }
@ -292,12 +289,12 @@ class ArticleFragment : Fragment(), DIAware {
e.sendSilentlyWithAcraWithName("getContentFromMercury > outside glide lead image") e.sendSilentlyWithAcraWithName("getContentFromMercury > outside glide lead image")
} }
} else { } else {
binding.imageView.visibility = View.GONE binding!!.imageView.visibility = View.GONE
} }
try { try {
binding.nestedScrollView.scrollTo(0, 0) binding!!.nestedScrollView.scrollTo(0, 0)
binding.progressBar.visibility = View.GONE binding!!.progressBar.visibility = View.GONE
} catch (e: Exception) { } catch (e: Exception) {
e.sendSilentlyWithAcraWithName("getContentFromMercury > scrollview") e.sendSilentlyWithAcraWithName("getContentFromMercury > scrollview")
} }
@ -320,8 +317,8 @@ class ArticleFragment : Fragment(), DIAware {
val a: TypedArray = requireContext().obtainStyledAttributes(resId, attrs) val a: TypedArray = requireContext().obtainStyledAttributes(resId, attrs)
binding.webcontent.settings.standardFontFamily = a.getString(0) binding!!.webcontent.settings.standardFontFamily = a.getString(0)
binding.webcontent.visibility = View.VISIBLE binding!!.webcontent.visibility = View.VISIBLE
val colorOnSurface = TypedValue() val colorOnSurface = TypedValue()
requireContext().theme.resolveAttribute(R.attr.colorOnSurface, colorOnSurface, true) requireContext().theme.resolveAttribute(R.attr.colorOnSurface, colorOnSurface, true)
@ -329,28 +326,28 @@ class ArticleFragment : Fragment(), DIAware {
val colorSurface = TypedValue() val colorSurface = TypedValue()
requireContext().theme.resolveAttribute(R.attr.colorSurface, colorSurface, true) requireContext().theme.resolveAttribute(R.attr.colorSurface, colorSurface, true)
binding.webcontent.settings.useWideViewPort = true binding!!.webcontent.settings.useWideViewPort = true
binding.webcontent.settings.loadWithOverviewMode = true binding!!.webcontent.settings.loadWithOverviewMode = true
binding.webcontent.settings.javaScriptEnabled = false binding!!.webcontent.settings.javaScriptEnabled = false
binding.webcontent.webViewClient = object : WebViewClient() { binding!!.webcontent.webViewClient = object : WebViewClient() {
@Deprecated("Deprecated in Java") @Deprecated("Deprecated in Java")
override fun shouldOverrideUrlLoading(view: WebView?, url : String): Boolean { override fun shouldOverrideUrlLoading(view: WebView?, url : String): Boolean {
if (binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) { if (binding!!.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
requireContext().startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) requireContext().startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
} }
return true return true
} }
@Deprecated("Deprecated in Java") @Deprecated("Deprecated in Java")
override fun shouldInterceptRequest(view: WebView?, url: String): WebResourceResponse? { override fun shouldInterceptRequest(view: WebView, url: String): WebResourceResponse? {
val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL) val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
if (url.lowercase(Locale.US).contains(".jpg") || url.lowercase(Locale.US).contains(".jpeg")) { if (url.lowercase(Locale.US).contains(".jpg") || url.lowercase(Locale.US).contains(".jpeg")) {
try { try {
val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get() val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.JPEG)) return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.JPEG))
} catch ( e : ExecutionException) { } catch ( e : ExecutionException) {
e.sendSilentlyWithAcraWithName("shouldInterceptRequest > jpeg") e.sendSilentlyWithAcraWithName("shouldInterceptRequest > jpeg > $url")
} }
} }
else if (url.lowercase(Locale.US).contains(".png")) { else if (url.lowercase(Locale.US).contains(".png")) {
@ -358,7 +355,7 @@ class ArticleFragment : Fragment(), DIAware {
val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get() val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.PNG)) return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.PNG))
} catch ( e : ExecutionException) { } catch ( e : ExecutionException) {
e.sendSilentlyWithAcraWithName("shouldInterceptRequest > png") e.sendSilentlyWithAcraWithName("shouldInterceptRequest > png > $url")
} }
} }
else if (url.lowercase(Locale.US).contains(".webp")) { else if (url.lowercase(Locale.US).contains(".webp")) {
@ -366,7 +363,7 @@ class ArticleFragment : Fragment(), DIAware {
val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get() val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.WEBP)) return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.WEBP))
} catch ( e : ExecutionException) { } catch ( e : ExecutionException) {
e.sendSilentlyWithAcraWithName("shouldInterceptRequest > webp") e.sendSilentlyWithAcraWithName("shouldInterceptRequest > webp > $url")
} }
} }
@ -380,9 +377,9 @@ class ArticleFragment : Fragment(), DIAware {
} }
}) })
binding.webcontent.setOnTouchListener { _, event -> gestureDetector.onTouchEvent(event)} binding!!.webcontent.setOnTouchListener { _, event -> gestureDetector.onTouchEvent(event)}
binding.webcontent.settings.layoutAlgorithm = binding!!.webcontent.settings.layoutAlgorithm =
WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
var baseUrl: String? = null var baseUrl: String? = null
@ -413,7 +410,7 @@ class ArticleFragment : Fragment(), DIAware {
"" ""
} }
binding.webcontent.loadDataWithBaseURL( binding!!.webcontent.loadDataWithBaseURL(
baseUrl, baseUrl,
"""<html> """<html>
|<head> |<head>
@ -466,17 +463,17 @@ class ArticleFragment : Fragment(), DIAware {
} }
fun scrollDown() { fun scrollDown() {
val height = binding.nestedScrollView.measuredHeight val height = binding!!.nestedScrollView.measuredHeight
binding.nestedScrollView.smoothScrollBy(0, height/2) binding!!.nestedScrollView.smoothScrollBy(0, height/2)
} }
fun scrollUp() { fun scrollUp() {
val height = binding.nestedScrollView.measuredHeight val height = binding!!.nestedScrollView.measuredHeight
binding.nestedScrollView.smoothScrollBy(0, -height/2) binding!!.nestedScrollView.smoothScrollBy(0, -height/2)
} }
private fun openInBrowserAfterFailing() { private fun openInBrowserAfterFailing() {
binding.progressBar.visibility = View.GONE binding!!.progressBar.visibility = View.GONE
requireActivity().openInBrowserAsNewTask(this@ArticleFragment.item) requireActivity().openInBrowserAsNewTask(this@ArticleFragment.item)
} }
@ -495,10 +492,10 @@ class ArticleFragment : Fragment(), DIAware {
} }
fun performClick(): Boolean { fun performClick(): Boolean {
if (binding.webcontent.hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE || if (binding!!.webcontent.hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE ||
binding.webcontent.hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) { binding!!.webcontent.hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
val position : Int = allImages.indexOf(binding.webcontent.hitTestResult.extra) val position : Int = allImages.indexOf(binding!!.webcontent.hitTestResult.extra)
val intent = Intent(activity, ImageActivity::class.java) val intent = Intent(activity, ImageActivity::class.java)
intent.putExtra("allImages", allImages) intent.putExtra("allImages", allImages)

View File

@ -1,6 +1,6 @@
package bou.amine.apps.readerforselfossv2.android.fragments package bou.amine.apps.readerforselfossv2.android.fragments
import android.annotation.SuppressLint import android.content.Context
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable import android.graphics.drawable.GradientDrawable
@ -13,9 +13,7 @@ import android.view.ViewGroup
import bou.amine.apps.readerforselfossv2.android.HomeActivity import bou.amine.apps.readerforselfossv2.android.HomeActivity
import bou.amine.apps.readerforselfossv2.android.R import bou.amine.apps.readerforselfossv2.android.R
import bou.amine.apps.readerforselfossv2.android.sendSilentlyWithAcraWithName import bou.amine.apps.readerforselfossv2.android.sendSilentlyWithAcraWithName
import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.repository.Repository import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
import bou.amine.apps.readerforselfossv2.utils.getIcon import bou.amine.apps.readerforselfossv2.utils.getIcon
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
@ -38,16 +36,14 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
override val di: DI by closestDI() override val di: DI by closestDI()
private val repository: Repository by instance() private val repository: Repository by instance()
private val appSettingsService: AppSettingsService by instance()
private var selectedChip: Chip? = null private var selectedChip: Chip? = null
@SuppressLint("ResourceAsColor")
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View {
val binding = val binding =
bou.amine.apps.readerforselfossv2.android.databinding.FilterFragmentBinding.inflate( bou.amine.apps.readerforselfossv2.android.databinding.FilterFragmentBinding.inflate(
inflater, inflater,
@ -55,87 +51,118 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
false false
) )
val context: Context? = context
val tagGroup = binding.tagsGroup val tagGroup = binding.tagsGroup
val hiddenTagGroup = binding.hiddenTagsGroup
val sourceGroup = binding.sourcesGroup val sourceGroup = binding.sourcesGroup
CoroutineScope(Dispatchers.Main).launch { if (context == null) {
val tags = repository.getTags() dismiss()
val hiddenTags = appSettingsService.getHiddenTags() Exception("FilterSheetFragment context is null").sendSilentlyWithAcraWithName("FilterSheetFragment > onCreateView")
} else {
CoroutineScope(Dispatchers.Main).launch {
val tags = repository.getTags()
tags.filterNot { hiddenTags.contains(it.tag) }.forEach { tag -> tags.forEach { tag ->
val c = chipForTag(tag) val c = Chip(context)
tagGroup.addView(c) c.text = tag.tag
}
if (hiddenTags.isNotEmpty()) { val gd = GradientDrawable()
binding.filterHiddenTagsTitle.visibility = VISIBLE val gdColor = try {
binding.hiddenTagsGroup.visibility = VISIBLE Color.parseColor(tag.color)
} catch (e: IllegalArgumentException) {
tags.filter { hiddenTags.contains(it.tag) }.forEach { tag -> e.sendSilentlyWithAcraWithName("color issue " + tag.color)
val c = chipForTag(tag) resources.getColor(R.color.colorPrimary)
hiddenTagGroup.addView(c)
}
}
repository.getSources().forEach { source ->
val c = Chip(requireContext())
Glide.with(requireContext())
.load(source.getIcon(repository.baseUrl))
.listener(object : RequestListener<Drawable?> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable?>?,
isFirstResource: Boolean
): Boolean {
return false
}
override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: Target<Drawable?>?,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
c.chipIcon = resource
return false
}
}).preload()
c.text = source.title.getHtmlDecoded()
c.setOnCloseIconClickListener {
(it as Chip).isCloseIconVisible = false
selectedChip = null
repository.setSourceFilter(null)
}
c.setOnClickListener {
if (selectedChip != null) {
selectedChip!!.isCloseIconVisible = false
} }
(it as Chip).isCloseIconVisible = true gd.setColor(gdColor)
selectedChip = it gd.shape = GradientDrawable.RECTANGLE
repository.setSourceFilter(source) gd.setSize(30, 30)
gd.cornerRadius = 30F
c.chipIcon = gd
repository.setTagFilter(null) c.setOnCloseIconClickListener {
(it as Chip).isCloseIconVisible = false
selectedChip = null
repository.setTagFilter(null)
}
c.setOnClickListener {
if (selectedChip != null) {
selectedChip!!.isCloseIconVisible = false
}
(it as Chip).isCloseIconVisible = true
selectedChip = it
repository.setTagFilter(tag)
repository.setSourceFilter(null)
}
if (repository.tagFilter.value?.equals(tag) == true) {
c.isCloseIconVisible = true
selectedChip = c
}
tagGroup.addView(c)
} }
repository.getSources().forEach { source ->
val c = Chip(context)
if (repository.sourceFilter.value?.equals(source) == true) { Glide.with(context)
c.isCloseIconVisible = true .load(source.getIcon(repository.baseUrl))
selectedChip = c .listener(object : RequestListener<Drawable?> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable?>?,
isFirstResource: Boolean
): Boolean {
return false
}
override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: Target<Drawable?>?,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
c.chipIcon = resource
return false
}
}).preload()
c.text = source.title.getHtmlDecoded()
c.setOnCloseIconClickListener {
(it as Chip).isCloseIconVisible = false
selectedChip = null
repository.setSourceFilter(null)
}
c.setOnClickListener {
if (selectedChip != null) {
selectedChip!!.isCloseIconVisible = false
}
(it as Chip).isCloseIconVisible = true
selectedChip = it
repository.setSourceFilter(source)
repository.setTagFilter(null)
}
if (repository.sourceFilter.value?.equals(source) == true) {
c.isCloseIconVisible = true
selectedChip = c
}
sourceGroup.addView(c)
} }
sourceGroup.addView(c) binding.progressBar2.visibility = GONE
binding.filterView.visibility = VISIBLE
} }
binding.progressBar2.visibility = GONE
binding.filterView.visibility = VISIBLE
} }
binding.floatingActionButton2.setOnClickListener { binding.floatingActionButton2.setOnClickListener {
@ -146,49 +173,8 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
return binding.root return binding.root
} }
private fun chipForTag(tag: SelfossModel.Tag): Chip {
val c = Chip(requireContext())
c.text = tag.tag
val gd = GradientDrawable()
val gdColor = try {
Color.parseColor(tag.color)
} catch (e: IllegalArgumentException) {
e.sendSilentlyWithAcraWithName("color issue " + tag.color)
resources.getColor(R.color.colorPrimary)
}
gd.setColor(gdColor)
gd.shape = GradientDrawable.RECTANGLE
gd.setSize(30, 30)
gd.cornerRadius = 30F
c.chipIcon = gd
c.setOnCloseIconClickListener {
(it as Chip).isCloseIconVisible = false
selectedChip = null
repository.setTagFilter(null)
}
c.setOnClickListener {
if (selectedChip != null) {
selectedChip!!.isCloseIconVisible = false
}
(it as Chip).isCloseIconVisible = true
selectedChip = it
repository.setTagFilter(tag)
repository.setSourceFilter(null)
}
if (repository.tagFilter.value?.equals(tag) == true) {
c.isCloseIconVisible = true
selectedChip = c
}
return c
}
companion object { companion object {
const val TAG = "ModalBottomSheet" const val TAG = "FilterModalBottomSheet"
} }

View File

@ -28,7 +28,7 @@ class ImageFragment : Fragment() {
val view = binding?.root val view = binding?.root
binding!!.photoView.visibility = View.VISIBLE binding!!.photoView.visibility = View.VISIBLE
Glide.with(activity) Glide.with(requireActivity())
.asBitmap() .asBitmap()
.apply(glideOptions) .apply(glideOptions)
.load(imageUrl) .load(imageUrl)

View File

@ -16,7 +16,8 @@ fun SelfossModel.Item.toParcelable() : ParecelableItem =
this.icon, this.icon,
this.link, this.link,
this.sourcetitle, this.sourcetitle,
this.tags.joinToString(",") this.tags.joinToString(","),
this.author
) )
fun ParecelableItem.toModel() : SelfossModel.Item = fun ParecelableItem.toModel() : SelfossModel.Item =
SelfossModel.Item( SelfossModel.Item(
@ -30,7 +31,8 @@ fun ParecelableItem.toModel() : SelfossModel.Item =
this.icon, this.icon,
this.link, this.link,
this.sourcetitle, this.sourcetitle,
this.tags.split(",") this.tags.split(","),
this.author
) )
data class ParecelableItem( data class ParecelableItem(
val id: Int, val id: Int,
@ -43,7 +45,8 @@ data class ParecelableItem(
val icon: String?, val icon: String?,
val link: String, val link: String,
val sourcetitle: String, val sourcetitle: String,
val tags: String val tags: String,
val author: String?
) : Parcelable { ) : Parcelable {
companion object { companion object {
@ -65,7 +68,8 @@ data class ParecelableItem(
icon = source.readString(), icon = source.readString(),
link = source.readString().orEmpty(), link = source.readString().orEmpty(),
sourcetitle = source.readString().orEmpty(), sourcetitle = source.readString().orEmpty(),
tags = source.readString().orEmpty() tags = source.readString().orEmpty(),
author = source.readString().orEmpty()
) )
override fun describeContents() = 0 override fun describeContents() = 0
@ -82,5 +86,6 @@ data class ParecelableItem(
dest.writeString(link) dest.writeString(link)
dest.writeString(sourcetitle) dest.writeString(sourcetitle)
dest.writeString(tags) dest.writeString(tags)
dest.writeString(author)
} }
} }

View File

@ -19,13 +19,9 @@ import bou.amine.apps.readerforselfossv2.android.databinding.ActivitySettingsBin
import bou.amine.apps.readerforselfossv2.android.sendSilentlyWithAcraWithName import bou.amine.apps.readerforselfossv2.android.sendSilentlyWithAcraWithName
import bou.amine.apps.readerforselfossv2.service.AppSettingsService import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import com.mikepenz.aboutlibraries.LibsBuilder import com.mikepenz.aboutlibraries.LibsBuilder
import org.acra.ktx.sendSilentlyWithAcra
import org.acra.ktx.sendWithAcra
import org.kodein.di.DIAware import org.kodein.di.DIAware
import org.kodein.di.android.closestDI import org.kodein.di.android.closestDI
import org.kodein.di.instance import org.kodein.di.instance
import org.matomo.sdk.Tracker
import org.matomo.sdk.extra.TrackHelper
private const val TITLE_TAG = "settingsActivityTitle" private const val TITLE_TAG = "settingsActivityTitle"
@ -33,14 +29,10 @@ class SettingsActivity : AppCompatActivity(),
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback, DIAware { PreferenceFragmentCompat.OnPreferenceStartFragmentCallback, DIAware {
override val di by closestDI() override val di by closestDI()
private val tracker : Tracker by instance()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val binding = ActivitySettingsBinding.inflate(layoutInflater) val binding = ActivitySettingsBinding.inflate(layoutInflater)
TrackHelper.track().screen("/settings").with(tracker)
setContentView(binding.root) setContentView(binding.root)
if (savedInstanceState == null) { if (savedInstanceState == null) {
supportFragmentManager supportFragmentManager

View File

@ -18,7 +18,6 @@ import bou.amine.apps.readerforselfossv2.utils.toStringUriWithHttp
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
fun Context.openItemUrl( fun Context.openItemUrl(
allItems: ArrayList<SelfossModel.Item>,
currentItem: Int, currentItem: Int,
linkDecoded: String, linkDecoded: String,
articleViewer: Boolean, articleViewer: Boolean,
@ -33,7 +32,6 @@ fun Context.openItemUrl(
).show() ).show()
} else { } else {
if (articleViewer) { if (articleViewer) {
ReaderActivity.allItems = allItems
val intent = Intent(this, ReaderActivity::class.java) val intent = Intent(this, ReaderActivity::class.java)
intent.putExtra("currentItem", currentItem) intent.putExtra("currentItem", currentItem)
app.startActivity(intent) app.startActivity(intent)

View File

@ -1,7 +0,0 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M21,8c-1.45,0 -2.26,1.44 -1.93,2.51l-3.55,3.56c-0.3,-0.09 -0.74,-0.09 -1.04,0l-2.55,-2.55C12.27,10.45 11.46,9 10,9c-1.45,0 -2.27,1.44 -1.93,2.52l-4.56,4.55C2.44,15.74 1,16.55 1,18c0,1.1 0.9,2 2,2c1.45,0 2.26,-1.44 1.93,-2.51l4.55,-4.56c0.3,0.09 0.74,0.09 1.04,0l2.55,2.55C12.73,16.55 13.54,18 15,18c1.45,0 2.27,-1.44 1.93,-2.52l3.56,-3.55C21.56,12.26 23,11.45 23,10C23,8.9 22.1,8 21,8z"/>
<path android:fillColor="@android:color/white" android:pathData="M15,9l0.94,-2.07l2.06,-0.93l-2.06,-0.93l-0.94,-2.07l-0.92,2.07l-2.08,0.93l2.08,0.93z"/>
<path android:fillColor="@android:color/white" android:pathData="M3.5,11l0.5,-2l2,-0.5l-2,-0.5l-0.5,-2l-0.5,2l-2,0.5l2,0.5z"/>
</vector>

View File

@ -62,33 +62,6 @@
</com.google.android.material.chip.ChipGroup> </com.google.android.material.chip.ChipGroup>
<TextView
android:id="@+id/filterHiddenTagsTitle"
style="@style/MaterialAlertDialog.MaterialComponents.Title.Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:text="@string/filter_item_hidden_tags"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tagsGroup" />
<com.google.android.material.chip.ChipGroup
android:id="@+id/hiddenTagsGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="24dp"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/filterHiddenTagsTitle"
app:singleSelection="true">
</com.google.android.material.chip.ChipGroup>
<TextView <TextView
android:id="@+id/filterSourcesTitle" android:id="@+id/filterSourcesTitle"
style="@style/MaterialAlertDialog.MaterialComponents.Title.Text" style="@style/MaterialAlertDialog.MaterialComponents.Title.Text"
@ -98,7 +71,7 @@
android:layout_marginTop="24dp" android:layout_marginTop="24dp"
android:text="@string/filter_item_sources" android:text="@string/filter_item_sources"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/hiddenTagsGroup" /> app:layout_constraintTop_toBottomOf="@+id/tagsGroup" />
<com.google.android.material.chip.ChipGroup <com.google.android.material.chip.ChipGroup

View File

@ -69,7 +69,6 @@
<string name="pref_header_theme">Temes</string> <string name="pref_header_theme">Temes</string>
<string name="pref_selfoss_category">API de Selfoss</string> <string name="pref_selfoss_category">API de Selfoss</string>
<string name="pref_api_items_number_title">Nombre d\'elements carregats</string> <string name="pref_api_items_number_title">Nombre d\'elements carregats</string>
<string name="pref_hidden_tags">Etiquetes ocultes</string>
<string name="pref_general_infinite_loading_title">Carrega articles en desplaçar</string> <string name="pref_general_infinite_loading_title">Carrega articles en desplaçar</string>
<string name="translation">Traducció</string> <string name="translation">Traducció</string>
<string name="cant_open_invalid_url">L\'element URL no és vàlid. Estic intentant solucionar aquest problema perquè l\'aplicació no falli.</string> <string name="cant_open_invalid_url">L\'element URL no és vàlid. Estic intentant solucionar aquest problema perquè l\'aplicació no falli.</string>
@ -84,7 +83,6 @@
<string name="markall_dialog_message">Aquesta acció marcarà els elements com a llegits.</string> <string name="markall_dialog_message">Aquesta acció marcarà els elements com a llegits.</string>
<string name="pref_switch_actions_pager_scroll">Marca com a llegit en lliscar el dit</string> <string name="pref_switch_actions_pager_scroll">Marca com a llegit en lliscar el dit</string>
<string name="pref_switch_actions_pager_scroll_off">No es marcaran els articles com a llegits en lliscar el dit d\'un article a l\'altre.</string> <string name="pref_switch_actions_pager_scroll_off">No es marcaran els articles com a llegits en lliscar el dit d\'un article a l\'altre.</string>
<string name="filter_item_hidden_tags">Etiquetes ocultes</string>
<string name="unmark">Marca com no llegit</string> <string name="unmark">Marca com no llegit</string>
<string name="pref_header_offline">Sense connexió i memòria clau</string> <string name="pref_header_offline">Sense connexió i memòria clau</string>
<string name="pref_switch_items_caching_off">Els articles no es guardaran a la memòria del dispositiu i l\'aplicació no es podrà utilitzar sense connexió.</string> <string name="pref_switch_items_caching_off">Els articles no es guardaran a la memòria del dispositiu i l\'aplicació no es podrà utilitzar sense connexió.</string>
@ -99,7 +97,7 @@
<string name="pref_switch_periodic_refresh_on">Els articles se sincronitzaran periòdicament</string> <string name="pref_switch_periodic_refresh_on">Els articles se sincronitzaran periòdicament</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Interval de sincronització ( >= 15 minuts)]]></string> <string name="pref_periodic_refresh_minutes_title"><![CDATA[Interval de sincronització ( >= 15 minuts)]]></string>
<string name="pref_switch_refresh_when_charging">Sincronitza només quan el telèfon s\'està carregant</string> <string name="pref_switch_refresh_when_charging">Sincronitza només quan el telèfon s\'està carregant</string>
<string name="loading_notification_title">S\'està carregant...</string> <string name="loading_notification_title">S\'està carregant</string>
<string name="loading_notification_text">Selfoss està sincronitzant els articles</string> <string name="loading_notification_text">Selfoss està sincronitzant els articles</string>
<string name="notification_channel_sync">Notificació de sincronització</string> <string name="notification_channel_sync">Notificació de sincronització</string>
<string name="new_items_channel_sync">Notificació d\'elements nous</string> <string name="new_items_channel_sync">Notificació d\'elements nous</string>
@ -122,10 +120,10 @@
<string name="mode_dark">Dark mode</string> <string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string> <string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string> <string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string> <string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string> <string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</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>
</resources> </resources>

View File

@ -69,7 +69,6 @@
<string name="pref_header_theme">Designs</string> <string name="pref_header_theme">Designs</string>
<string name="pref_selfoss_category">selfoss API</string> <string name="pref_selfoss_category">selfoss API</string>
<string name="pref_api_items_number_title">Loaded items number</string> <string name="pref_api_items_number_title">Loaded items number</string>
<string name="pref_hidden_tags">Hidden Tags</string>
<string name="pref_general_infinite_loading_title">Load more articles on scroll</string> <string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Übersetzung</string> <string name="translation">Übersetzung</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string> <string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
@ -84,7 +83,6 @@
<string name="markall_dialog_message">Dies wird alle Elemente als gelesen markieren.</string> <string name="markall_dialog_message">Dies wird alle Elemente als gelesen markieren.</string>
<string name="pref_switch_actions_pager_scroll">Beim Wischen als gelesen markieren</string> <string name="pref_switch_actions_pager_scroll">Beim Wischen als gelesen markieren</string>
<string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string> <string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string>
<string name="filter_item_hidden_tags">Hidden Tags</string>
<string name="unmark">Eintrag als ungelesen markieren</string> <string name="unmark">Eintrag als ungelesen markieren</string>
<string name="pref_header_offline">Offline and cache</string> <string name="pref_header_offline">Offline and cache</string>
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string> <string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
@ -99,7 +97,7 @@
<string name="pref_switch_periodic_refresh_on">Die Artikel werden regelmäßig synchronisiert</string> <string name="pref_switch_periodic_refresh_on">Die Artikel werden regelmäßig synchronisiert</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string> <string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string>
<string name="pref_switch_refresh_when_charging">Nur aktualisieren, wenn das Telefon aufgeladen wird</string> <string name="pref_switch_refresh_when_charging">Nur aktualisieren, wenn das Telefon aufgeladen wird</string>
<string name="loading_notification_title">Lädt...</string> <string name="loading_notification_title">Lädt</string>
<string name="loading_notification_text">Selfoss synchronisiert Ihre Artikel</string> <string name="loading_notification_text">Selfoss synchronisiert Ihre Artikel</string>
<string name="notification_channel_sync">Sync notification</string> <string name="notification_channel_sync">Sync notification</string>
<string name="new_items_channel_sync">New items notification</string> <string name="new_items_channel_sync">New items notification</string>
@ -122,10 +120,10 @@
<string name="mode_dark">Dark mode</string> <string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string> <string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string> <string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string> <string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string> <string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</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>
</resources> </resources>

View File

@ -69,7 +69,6 @@
<string name="pref_header_theme">Temas</string> <string name="pref_header_theme">Temas</string>
<string name="pref_selfoss_category">Api de Selfoss</string> <string name="pref_selfoss_category">Api de Selfoss</string>
<string name="pref_api_items_number_title">Número de artículos cargados</string> <string name="pref_api_items_number_title">Número de artículos cargados</string>
<string name="pref_hidden_tags">Etiquetas ocultas</string>
<string name="pref_general_infinite_loading_title">Cargar más artículos en desplazamiento</string> <string name="pref_general_infinite_loading_title">Cargar más artículos en desplazamiento</string>
<string name="translation">Traducción</string> <string name="translation">Traducción</string>
<string name="cant_open_invalid_url">La url del elemento no es válida. Estoy buscando resolver este problema para que la aplicación no colapse.</string> <string name="cant_open_invalid_url">La url del elemento no es válida. Estoy buscando resolver este problema para que la aplicación no colapse.</string>
@ -84,7 +83,6 @@
<string name="markall_dialog_message">Esto marcará todos los artículos como leídos.</string> <string name="markall_dialog_message">Esto marcará todos los artículos como leídos.</string>
<string name="pref_switch_actions_pager_scroll">Marcar artículos como leídos al deslizar con el dedo hacia los lados</string> <string name="pref_switch_actions_pager_scroll">Marcar artículos como leídos al deslizar con el dedo hacia los lados</string>
<string name="pref_switch_actions_pager_scroll_off">No marcar artículos como leídos al deslizar con el dedo hacia los lados.</string> <string name="pref_switch_actions_pager_scroll_off">No marcar artículos como leídos al deslizar con el dedo hacia los lados.</string>
<string name="filter_item_hidden_tags">Etiquetas ocultas</string>
<string name="unmark">Marcar artículo como no leído</string> <string name="unmark">Marcar artículo como no leído</string>
<string name="pref_header_offline">Sin conexión y caché</string> <string name="pref_header_offline">Sin conexión y caché</string>
<string name="pref_switch_items_caching_off">Los artículos no se guardarán en la memoria del dispositivo y la aplicación no se podrá utilizar sin conexión.</string> <string name="pref_switch_items_caching_off">Los artículos no se guardarán en la memoria del dispositivo y la aplicación no se podrá utilizar sin conexión.</string>
@ -99,7 +97,7 @@
<string name="pref_switch_periodic_refresh_on">Los artículos se sincronizarán periódicamente</string> <string name="pref_switch_periodic_refresh_on">Los artículos se sincronizarán periódicamente</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Intervalo de sincronización (>= 15 minutos)]]></string> <string name="pref_periodic_refresh_minutes_title"><![CDATA[Intervalo de sincronización (>= 15 minutos)]]></string>
<string name="pref_switch_refresh_when_charging">Sólo refrescar cuando el teléfono está cargando</string> <string name="pref_switch_refresh_when_charging">Sólo refrescar cuando el teléfono está cargando</string>
<string name="loading_notification_title">Cargando...</string> <string name="loading_notification_title">Cargando</string>
<string name="loading_notification_text">Selfoss está sincronizando tus artículos</string> <string name="loading_notification_text">Selfoss está sincronizando tus artículos</string>
<string name="notification_channel_sync">Notificación de sincronización</string> <string name="notification_channel_sync">Notificación de sincronización</string>
<string name="new_items_channel_sync">Notificación de elementos nuevos</string> <string name="new_items_channel_sync">Notificación de elementos nuevos</string>
@ -122,10 +120,10 @@
<string name="mode_dark">Dark mode</string> <string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string> <string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string> <string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string> <string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string> <string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</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>
</resources> </resources>

View File

@ -69,7 +69,6 @@
<string name="pref_header_theme">Themes</string> <string name="pref_header_theme">Themes</string>
<string name="pref_selfoss_category">Selfoss Api</string> <string name="pref_selfoss_category">Selfoss Api</string>
<string name="pref_api_items_number_title">Loaded items number</string> <string name="pref_api_items_number_title">Loaded items number</string>
<string name="pref_hidden_tags">Hidden Tags</string>
<string name="pref_general_infinite_loading_title">Load more articles on scroll</string> <string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Translation</string> <string name="translation">Translation</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string> <string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
@ -84,7 +83,6 @@
<string name="markall_dialog_message">This will mark all the items as read.</string> <string name="markall_dialog_message">This will mark all the items as read.</string>
<string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string> <string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string>
<string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string> <string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string>
<string name="filter_item_hidden_tags">Hidden Tags</string>
<string name="unmark">Mark item as unread</string> <string name="unmark">Mark item as unread</string>
<string name="pref_header_offline">Offline and cache</string> <string name="pref_header_offline">Offline and cache</string>
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string> <string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
@ -99,7 +97,7 @@
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string> <string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string> <string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string>
<string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string> <string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string>
<string name="loading_notification_title">Loading ...</string> <string name="loading_notification_title">Loading </string>
<string name="loading_notification_text">Selfoss is syncing your articles</string> <string name="loading_notification_text">Selfoss is syncing your articles</string>
<string name="notification_channel_sync">Sync notification</string> <string name="notification_channel_sync">Sync notification</string>
<string name="new_items_channel_sync">New items notification</string> <string name="new_items_channel_sync">New items notification</string>
@ -122,10 +120,10 @@
<string name="mode_dark">Dark mode</string> <string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string> <string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string> <string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string> <string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string> <string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</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>
</resources> </resources>

View File

@ -69,7 +69,6 @@
<string name="pref_header_theme">Thèmes</string> <string name="pref_header_theme">Thèmes</string>
<string name="pref_selfoss_category">Api Selfoss</string> <string name="pref_selfoss_category">Api Selfoss</string>
<string name="pref_api_items_number_title">Nombre d\'articles chargés</string> <string name="pref_api_items_number_title">Nombre d\'articles chargés</string>
<string name="pref_hidden_tags">Tags Cachés</string>
<string name="pref_general_infinite_loading_title">Charger plus d\'articles au scroll</string> <string name="pref_general_infinite_loading_title">Charger plus d\'articles au scroll</string>
<string name="translation">Traduction</string> <string name="translation">Traduction</string>
<string name="cant_open_invalid_url">Lurl de lélément nest pas valide. En attendant la résolution du problème, le lien ne s\'ouvrira pas.</string> <string name="cant_open_invalid_url">Lurl de lélément nest pas valide. En attendant la résolution du problème, le lien ne s\'ouvrira pas.</string>
@ -84,7 +83,6 @@
<string name="markall_dialog_message">Marquer tous les éléments comme lus ?</string> <string name="markall_dialog_message">Marquer tous les éléments comme lus ?</string>
<string name="pref_switch_actions_pager_scroll">Marquer comme lu à la navigation.</string> <string name="pref_switch_actions_pager_scroll">Marquer comme lu à la navigation.</string>
<string name="pref_switch_actions_pager_scroll_off">Ne pas marquer les articles comme lus à la navigation.</string> <string name="pref_switch_actions_pager_scroll_off">Ne pas marquer les articles comme lus à la navigation.</string>
<string name="filter_item_hidden_tags">Tags Cachés</string>
<string name="unmark">Marquer l\'article comme non lu</string> <string name="unmark">Marquer l\'article comme non lu</string>
<string name="pref_header_offline">Hors ligne et cache</string> <string name="pref_header_offline">Hors ligne et cache</string>
<string name="pref_switch_items_caching_off">Les articles ne seront pas enregistrés et l\'application ne sera pas utilisable hors ligne.</string> <string name="pref_switch_items_caching_off">Les articles ne seront pas enregistrés et l\'application ne sera pas utilisable hors ligne.</string>
@ -99,7 +97,7 @@
<string name="pref_switch_periodic_refresh_on">Articles seront périodiquement synchronisées</string> <string name="pref_switch_periodic_refresh_on">Articles seront périodiquement synchronisées</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Interval de synchronisation ( >= 15 minutes)]]></string> <string name="pref_periodic_refresh_minutes_title"><![CDATA[Interval de synchronisation ( >= 15 minutes)]]></string>
<string name="pref_switch_refresh_when_charging">Synchroniser uniquement lorsque le téléphone est en charge</string> <string name="pref_switch_refresh_when_charging">Synchroniser uniquement lorsque le téléphone est en charge</string>
<string name="loading_notification_title">Chargement ...</string> <string name="loading_notification_title">Chargement </string>
<string name="loading_notification_text">Selfoss synchronise vos articles</string> <string name="loading_notification_text">Selfoss synchronise vos articles</string>
<string name="notification_channel_sync">Notification de synchronisation</string> <string name="notification_channel_sync">Notification de synchronisation</string>
<string name="new_items_channel_sync">Notification de nouveaux articles</string> <string name="new_items_channel_sync">Notification de nouveaux articles</string>
@ -122,10 +120,10 @@
<string name="mode_dark">Thème sombre</string> <string name="mode_dark">Thème sombre</string>
<string name="mode_system">Utiliser les paramètres système</string> <string name="mode_system">Utiliser les paramètres système</string>
<string name="mode_light">Thème clair</string> <string name="mode_light">Thème clair</string>
<string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string> <string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string> <string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string> <string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</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>
</resources> </resources>

View File

@ -69,7 +69,6 @@
<string name="pref_header_theme">Temas</string> <string name="pref_header_theme">Temas</string>
<string name="pref_selfoss_category">API de Selfoss</string> <string name="pref_selfoss_category">API de Selfoss</string>
<string name="pref_api_items_number_title">Número de elementos cargados</string> <string name="pref_api_items_number_title">Número de elementos cargados</string>
<string name="pref_hidden_tags">Etiquetas ocultas</string>
<string name="pref_general_infinite_loading_title">Cargar máis artigos ao desprazarse</string> <string name="pref_general_infinite_loading_title">Cargar máis artigos ao desprazarse</string>
<string name="translation">Traducción</string> <string name="translation">Traducción</string>
<string name="cant_open_invalid_url">A URL do elemento non é válida. Estou tratando de solucionar isto pra que a aplicación non falle.</string> <string name="cant_open_invalid_url">A URL do elemento non é válida. Estou tratando de solucionar isto pra que a aplicación non falle.</string>
@ -84,7 +83,6 @@
<string name="markall_dialog_message">Isto marcara todos os elementos como lidos.</string> <string name="markall_dialog_message">Isto marcara todos os elementos como lidos.</string>
<string name="pref_switch_actions_pager_scroll">Marcar artigos como lidos ao deslizar co dedo cara os lados</string> <string name="pref_switch_actions_pager_scroll">Marcar artigos como lidos ao deslizar co dedo cara os lados</string>
<string name="pref_switch_actions_pager_scroll_off">Non marcar artigos como lidos ao deslizar co dedo cara os lados.</string> <string name="pref_switch_actions_pager_scroll_off">Non marcar artigos como lidos ao deslizar co dedo cara os lados.</string>
<string name="filter_item_hidden_tags">Etiquetas ocultas</string>
<string name="unmark">Marcar artículo como non lido</string> <string name="unmark">Marcar artículo como non lido</string>
<string name="pref_header_offline">Sen conexión e caché</string> <string name="pref_header_offline">Sen conexión e caché</string>
<string name="pref_switch_items_caching_off">Os artigos non se gardaran na memoria do dispositivo e non se poderá utilizar a aplicación sen conexión.</string> <string name="pref_switch_items_caching_off">Os artigos non se gardaran na memoria do dispositivo e non se poderá utilizar a aplicación sen conexión.</string>
@ -99,7 +97,7 @@
<string name="pref_switch_periodic_refresh_on">Os artigos sincronizaranse periódicamente</string> <string name="pref_switch_periodic_refresh_on">Os artigos sincronizaranse periódicamente</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Intervalo de sincronización (>= 15 minutos)]]></string> <string name="pref_periodic_refresh_minutes_title"><![CDATA[Intervalo de sincronización (>= 15 minutos)]]></string>
<string name="pref_switch_refresh_when_charging">Só refrescar cando o teléfono se está a cargar</string> <string name="pref_switch_refresh_when_charging">Só refrescar cando o teléfono se está a cargar</string>
<string name="loading_notification_title">Cargando...</string> <string name="loading_notification_title">Cargando</string>
<string name="loading_notification_text">Selfoss está sincronizando os teus ar tigos</string> <string name="loading_notification_text">Selfoss está sincronizando os teus ar tigos</string>
<string name="notification_channel_sync">Notificación de sincronización</string> <string name="notification_channel_sync">Notificación de sincronización</string>
<string name="new_items_channel_sync">Notificación de actualizacións</string> <string name="new_items_channel_sync">Notificación de actualizacións</string>
@ -122,10 +120,10 @@
<string name="mode_dark">Dark mode</string> <string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string> <string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string> <string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string> <string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string> <string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</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>
</resources> </resources>

View File

@ -69,7 +69,6 @@
<string name="pref_header_theme">Tema</string> <string name="pref_header_theme">Tema</string>
<string name="pref_selfoss_category">Selfoss Api</string> <string name="pref_selfoss_category">Selfoss Api</string>
<string name="pref_api_items_number_title">Item nomor dimuat</string> <string name="pref_api_items_number_title">Item nomor dimuat</string>
<string name="pref_hidden_tags">Hidden Tags</string>
<string name="pref_general_infinite_loading_title">Muat lebih banyak artikel saat membalik halaman</string> <string name="pref_general_infinite_loading_title">Muat lebih banyak artikel saat membalik halaman</string>
<string name="translation">Terjemahan</string> <string name="translation">Terjemahan</string>
<string name="cant_open_invalid_url">Alamat tautan proyek tidak valid. Saya mencoba memecahkan masalah ini untuk menghindari aplikasi berhenti.</string> <string name="cant_open_invalid_url">Alamat tautan proyek tidak valid. Saya mencoba memecahkan masalah ini untuk menghindari aplikasi berhenti.</string>
@ -84,7 +83,6 @@
<string name="markall_dialog_message">This will mark all the items as read.</string> <string name="markall_dialog_message">This will mark all the items as read.</string>
<string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string> <string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string>
<string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string> <string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string>
<string name="filter_item_hidden_tags">Hidden Tags</string>
<string name="unmark">Mark item as unread</string> <string name="unmark">Mark item as unread</string>
<string name="pref_header_offline">Offline and cache</string> <string name="pref_header_offline">Offline and cache</string>
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string> <string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
@ -99,7 +97,7 @@
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string> <string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string> <string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string>
<string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string> <string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string>
<string name="loading_notification_title">Loading ...</string> <string name="loading_notification_title">Loading </string>
<string name="loading_notification_text">Selfoss is syncing your articles</string> <string name="loading_notification_text">Selfoss is syncing your articles</string>
<string name="notification_channel_sync">Sync notification</string> <string name="notification_channel_sync">Sync notification</string>
<string name="new_items_channel_sync">New items notification</string> <string name="new_items_channel_sync">New items notification</string>
@ -122,10 +120,10 @@
<string name="mode_dark">Dark mode</string> <string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string> <string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string> <string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string> <string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string> <string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</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>
</resources> </resources>

View File

@ -69,7 +69,6 @@
<string name="pref_header_theme">Temi</string> <string name="pref_header_theme">Temi</string>
<string name="pref_selfoss_category">Api di Selfoss</string> <string name="pref_selfoss_category">Api di Selfoss</string>
<string name="pref_api_items_number_title">Numero di elementi caricati</string> <string name="pref_api_items_number_title">Numero di elementi caricati</string>
<string name="pref_hidden_tags">Tag nascosti</string>
<string name="pref_general_infinite_loading_title">Load more articles on scroll</string> <string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Traduzioni</string> <string name="translation">Traduzioni</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string> <string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
@ -84,7 +83,6 @@
<string name="markall_dialog_message">This will mark all the items as read.</string> <string name="markall_dialog_message">This will mark all the items as read.</string>
<string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string> <string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string>
<string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string> <string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string>
<string name="filter_item_hidden_tags">Hidden Tags</string>
<string name="unmark">Segna come non letto</string> <string name="unmark">Segna come non letto</string>
<string name="pref_header_offline">Offline and cache</string> <string name="pref_header_offline">Offline and cache</string>
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string> <string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
@ -99,7 +97,7 @@
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string> <string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string> <string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string>
<string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string> <string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string>
<string name="loading_notification_title">Loading ...</string> <string name="loading_notification_title">Loading </string>
<string name="loading_notification_text">Selfoss is syncing your articles</string> <string name="loading_notification_text">Selfoss is syncing your articles</string>
<string name="notification_channel_sync">Sync notification</string> <string name="notification_channel_sync">Sync notification</string>
<string name="new_items_channel_sync">New items notification</string> <string name="new_items_channel_sync">New items notification</string>
@ -122,10 +120,10 @@
<string name="mode_dark">Dark mode</string> <string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string> <string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string> <string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string> <string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string> <string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</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>
</resources> </resources>

View File

@ -69,7 +69,6 @@
<string name="pref_header_theme">Themes</string> <string name="pref_header_theme">Themes</string>
<string name="pref_selfoss_category">Selfoss Api</string> <string name="pref_selfoss_category">Selfoss Api</string>
<string name="pref_api_items_number_title">Loaded items number</string> <string name="pref_api_items_number_title">Loaded items number</string>
<string name="pref_hidden_tags">Hidden Tags</string>
<string name="pref_general_infinite_loading_title">Load more articles on scroll</string> <string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Translation</string> <string name="translation">Translation</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string> <string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
@ -84,7 +83,6 @@
<string name="markall_dialog_message">This will mark all the items as read.</string> <string name="markall_dialog_message">This will mark all the items as read.</string>
<string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string> <string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string>
<string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string> <string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string>
<string name="filter_item_hidden_tags">Hidden Tags</string>
<string name="unmark">Mark item as unread</string> <string name="unmark">Mark item as unread</string>
<string name="pref_header_offline">Offline and cache</string> <string name="pref_header_offline">Offline and cache</string>
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string> <string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
@ -99,7 +97,7 @@
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string> <string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string> <string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string>
<string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string> <string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string>
<string name="loading_notification_title">Loading ...</string> <string name="loading_notification_title">Loading </string>
<string name="loading_notification_text">Selfoss is syncing your articles</string> <string name="loading_notification_text">Selfoss is syncing your articles</string>
<string name="notification_channel_sync">Sync notification</string> <string name="notification_channel_sync">Sync notification</string>
<string name="new_items_channel_sync">New items notification</string> <string name="new_items_channel_sync">New items notification</string>
@ -122,10 +120,10 @@
<string name="mode_dark">Dark mode</string> <string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string> <string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string> <string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string> <string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string> <string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</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>
</resources> </resources>

View File

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string> <string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string> <string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string> <string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</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>
</resources> </resources>

View File

@ -69,7 +69,6 @@
<string name="pref_header_theme">Thema \'s</string> <string name="pref_header_theme">Thema \'s</string>
<string name="pref_selfoss_category">Selfoss Api</string> <string name="pref_selfoss_category">Selfoss Api</string>
<string name="pref_api_items_number_title">Geladen items nummer</string> <string name="pref_api_items_number_title">Geladen items nummer</string>
<string name="pref_hidden_tags">Hidden Tags</string>
<string name="pref_general_infinite_loading_title">Laad meer artikelen door te bladeren</string> <string name="pref_general_infinite_loading_title">Laad meer artikelen door te bladeren</string>
<string name="translation">Vertaling</string> <string name="translation">Vertaling</string>
<string name="cant_open_invalid_url">De URL is ongeldig. Ik probeer dit probleem op te lossen, zodat de toepassing niet wordt afgesloten.</string> <string name="cant_open_invalid_url">De URL is ongeldig. Ik probeer dit probleem op te lossen, zodat de toepassing niet wordt afgesloten.</string>
@ -84,7 +83,6 @@
<string name="markall_dialog_message">This will mark all the items as read.</string> <string name="markall_dialog_message">This will mark all the items as read.</string>
<string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string> <string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string>
<string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string> <string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string>
<string name="filter_item_hidden_tags">Hidden Tags</string>
<string name="unmark">Mark item as unread</string> <string name="unmark">Mark item as unread</string>
<string name="pref_header_offline">Offline and cache</string> <string name="pref_header_offline">Offline and cache</string>
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string> <string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
@ -99,7 +97,7 @@
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string> <string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string> <string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string>
<string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string> <string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string>
<string name="loading_notification_title">Loading ...</string> <string name="loading_notification_title">Loading </string>
<string name="loading_notification_text">Selfoss is syncing your articles</string> <string name="loading_notification_text">Selfoss is syncing your articles</string>
<string name="notification_channel_sync">Sync notification</string> <string name="notification_channel_sync">Sync notification</string>
<string name="new_items_channel_sync">New items notification</string> <string name="new_items_channel_sync">New items notification</string>
@ -122,10 +120,10 @@
<string name="mode_dark">Dark mode</string> <string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string> <string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string> <string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string> <string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string> <string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</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>
</resources> </resources>

View File

@ -69,7 +69,6 @@
<string name="pref_header_theme">Temas</string> <string name="pref_header_theme">Temas</string>
<string name="pref_selfoss_category">Selfoss Api</string> <string name="pref_selfoss_category">Selfoss Api</string>
<string name="pref_api_items_number_title">Quantidade de itens carregados</string> <string name="pref_api_items_number_title">Quantidade de itens carregados</string>
<string name="pref_hidden_tags">Hidden Tags</string>
<string name="pref_general_infinite_loading_title">Carregar mais artigos ao realizar o scroll</string> <string name="pref_general_infinite_loading_title">Carregar mais artigos ao realizar o scroll</string>
<string name="translation">Traduções</string> <string name="translation">Traduções</string>
<string name="cant_open_invalid_url">A url está inválida. Estou tentando resolver esse problema para que o aplicativo não encerre.</string> <string name="cant_open_invalid_url">A url está inválida. Estou tentando resolver esse problema para que o aplicativo não encerre.</string>
@ -84,7 +83,6 @@
<string name="markall_dialog_message">Isso marcará todos os itens como lidos.</string> <string name="markall_dialog_message">Isso marcará todos os itens como lidos.</string>
<string name="pref_switch_actions_pager_scroll">Marcar Como Lida ao Abrir</string> <string name="pref_switch_actions_pager_scroll">Marcar Como Lida ao Abrir</string>
<string name="pref_switch_actions_pager_scroll_off">Não marca artigos como lido quando abrir.</string> <string name="pref_switch_actions_pager_scroll_off">Não marca artigos como lido quando abrir.</string>
<string name="filter_item_hidden_tags">Hidden Tags</string>
<string name="unmark">Mark item as unread</string> <string name="unmark">Mark item as unread</string>
<string name="pref_header_offline">Offline and cache</string> <string name="pref_header_offline">Offline and cache</string>
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string> <string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
@ -99,7 +97,7 @@
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string> <string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string> <string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string>
<string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string> <string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string>
<string name="loading_notification_title">Loading ...</string> <string name="loading_notification_title">Loading </string>
<string name="loading_notification_text">Selfoss is syncing your articles</string> <string name="loading_notification_text">Selfoss is syncing your articles</string>
<string name="notification_channel_sync">Sync notification</string> <string name="notification_channel_sync">Sync notification</string>
<string name="new_items_channel_sync">New items notification</string> <string name="new_items_channel_sync">New items notification</string>
@ -122,10 +120,10 @@
<string name="mode_dark">Dark mode</string> <string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string> <string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string> <string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string> <string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string> <string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</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>
</resources> </resources>

View File

@ -69,7 +69,6 @@
<string name="pref_header_theme">Temas</string> <string name="pref_header_theme">Temas</string>
<string name="pref_selfoss_category">Api de Selfoss</string> <string name="pref_selfoss_category">Api de Selfoss</string>
<string name="pref_api_items_number_title">Número de itens carregados</string> <string name="pref_api_items_number_title">Número de itens carregados</string>
<string name="pref_hidden_tags">Hidden Tags</string>
<string name="pref_general_infinite_loading_title">Carregar mais artigos no pergaminho</string> <string name="pref_general_infinite_loading_title">Carregar mais artigos no pergaminho</string>
<string name="translation">Tradução</string> <string name="translation">Tradução</string>
<string name="cant_open_invalid_url">A url do item é inválido. Eu estou olhando para resolver esta questão, para que o app não vai falhar.</string> <string name="cant_open_invalid_url">A url do item é inválido. Eu estou olhando para resolver esta questão, para que o app não vai falhar.</string>
@ -84,7 +83,6 @@
<string name="markall_dialog_message">This will mark all the items as read.</string> <string name="markall_dialog_message">This will mark all the items as read.</string>
<string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string> <string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string>
<string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string> <string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string>
<string name="filter_item_hidden_tags">Hidden Tags</string>
<string name="unmark">Mark item as unread</string> <string name="unmark">Mark item as unread</string>
<string name="pref_header_offline">Offline and cache</string> <string name="pref_header_offline">Offline and cache</string>
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string> <string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
@ -99,7 +97,7 @@
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string> <string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string> <string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string>
<string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string> <string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string>
<string name="loading_notification_title">Loading ...</string> <string name="loading_notification_title">Loading </string>
<string name="loading_notification_text">Selfoss is syncing your articles</string> <string name="loading_notification_text">Selfoss is syncing your articles</string>
<string name="notification_channel_sync">Sync notification</string> <string name="notification_channel_sync">Sync notification</string>
<string name="new_items_channel_sync">New items notification</string> <string name="new_items_channel_sync">New items notification</string>
@ -122,10 +120,10 @@
<string name="mode_dark">Dark mode</string> <string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string> <string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string> <string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string> <string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string> <string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</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>
</resources> </resources>

View File

@ -69,7 +69,6 @@
<string name="pref_header_theme">Themes</string> <string name="pref_header_theme">Themes</string>
<string name="pref_selfoss_category">Selfoss Api</string> <string name="pref_selfoss_category">Selfoss Api</string>
<string name="pref_api_items_number_title">Loaded items number</string> <string name="pref_api_items_number_title">Loaded items number</string>
<string name="pref_hidden_tags">Hidden Tags</string>
<string name="pref_general_infinite_loading_title">Load more articles on scroll</string> <string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Translation</string> <string name="translation">Translation</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string> <string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
@ -84,7 +83,6 @@
<string name="markall_dialog_message">This will mark all the items as read.</string> <string name="markall_dialog_message">This will mark all the items as read.</string>
<string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string> <string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string>
<string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string> <string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string>
<string name="filter_item_hidden_tags">Hidden Tags</string>
<string name="unmark">Mark item as unread</string> <string name="unmark">Mark item as unread</string>
<string name="pref_header_offline">Offline and cache</string> <string name="pref_header_offline">Offline and cache</string>
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string> <string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
@ -99,7 +97,7 @@
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string> <string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string> <string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string>
<string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string> <string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string>
<string name="loading_notification_title">Loading ...</string> <string name="loading_notification_title">Loading </string>
<string name="loading_notification_text">Selfoss is syncing your articles</string> <string name="loading_notification_text">Selfoss is syncing your articles</string>
<string name="notification_channel_sync">Sync notification</string> <string name="notification_channel_sync">Sync notification</string>
<string name="new_items_channel_sync">New items notification</string> <string name="new_items_channel_sync">New items notification</string>
@ -122,10 +120,10 @@
<string name="mode_dark">Dark mode</string> <string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string> <string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string> <string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string> <string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string> <string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</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>
</resources> </resources>

View File

@ -69,7 +69,6 @@
<string name="pref_header_theme">Temalar</string> <string name="pref_header_theme">Temalar</string>
<string name="pref_selfoss_category">Selfoss Uygulaması</string> <string name="pref_selfoss_category">Selfoss Uygulaması</string>
<string name="pref_api_items_number_title">Yüklenen öğe numarası</string> <string name="pref_api_items_number_title">Yüklenen öğe numarası</string>
<string name="pref_hidden_tags">Hidden Tags</string>
<string name="pref_general_infinite_loading_title">Kaydırma üzerine daha fazla makale yükleyin</string> <string name="pref_general_infinite_loading_title">Kaydırma üzerine daha fazla makale yükleyin</string>
<string name="translation">Çeviri</string> <string name="translation">Çeviri</string>
<string name="cant_open_invalid_url">Öğe url geçersiz. Uygulama çökmeyeceği için bu sorunu çözmeye çalışıyorum.</string> <string name="cant_open_invalid_url">Öğe url geçersiz. Uygulama çökmeyeceği için bu sorunu çözmeye çalışıyorum.</string>
@ -84,7 +83,6 @@
<string name="markall_dialog_message">This will mark all the items as read.</string> <string name="markall_dialog_message">This will mark all the items as read.</string>
<string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string> <string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string>
<string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string> <string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string>
<string name="filter_item_hidden_tags">Hidden Tags</string>
<string name="unmark">Mark item as unread</string> <string name="unmark">Mark item as unread</string>
<string name="pref_header_offline">Offline and cache</string> <string name="pref_header_offline">Offline and cache</string>
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string> <string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
@ -99,7 +97,7 @@
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string> <string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string> <string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string>
<string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string> <string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string>
<string name="loading_notification_title">Loading ...</string> <string name="loading_notification_title">Loading </string>
<string name="loading_notification_text">Selfoss is syncing your articles</string> <string name="loading_notification_text">Selfoss is syncing your articles</string>
<string name="notification_channel_sync">Sync notification</string> <string name="notification_channel_sync">Sync notification</string>
<string name="new_items_channel_sync">New items notification</string> <string name="new_items_channel_sync">New items notification</string>
@ -122,10 +120,10 @@
<string name="mode_dark">Dark mode</string> <string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string> <string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string> <string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string> <string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string> <string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</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>
</resources> </resources>

View File

@ -69,7 +69,6 @@
<string name="pref_header_theme">主题</string> <string name="pref_header_theme">主题</string>
<string name="pref_selfoss_category">塞尔福斯 Api</string> <string name="pref_selfoss_category">塞尔福斯 Api</string>
<string name="pref_api_items_number_title">已加载项目编号</string> <string name="pref_api_items_number_title">已加载项目编号</string>
<string name="pref_hidden_tags">隐藏标签</string>
<string name="pref_general_infinite_loading_title">翻页时载入更多文章</string> <string name="pref_general_infinite_loading_title">翻页时载入更多文章</string>
<string name="translation">翻译</string> <string name="translation">翻译</string>
<string name="cant_open_invalid_url">项目链接地址无效。我正在设法解决这个问题,以避免应用程序崩溃。</string> <string name="cant_open_invalid_url">项目链接地址无效。我正在设法解决这个问题,以避免应用程序崩溃。</string>
@ -84,7 +83,6 @@
<string name="markall_dialog_message">这将标记所有项目为已读。</string> <string name="markall_dialog_message">这将标记所有项目为已读。</string>
<string name="pref_switch_actions_pager_scroll">滑动时标为已读</string> <string name="pref_switch_actions_pager_scroll">滑动时标为已读</string>
<string name="pref_switch_actions_pager_scroll_off">滑动时不标记文章为已读</string> <string name="pref_switch_actions_pager_scroll_off">滑动时不标记文章为已读</string>
<string name="filter_item_hidden_tags">隐藏标签</string>
<string name="unmark">标记条目为未读</string> <string name="unmark">标记条目为未读</string>
<string name="pref_header_offline">离线和缓存</string> <string name="pref_header_offline">离线和缓存</string>
<string name="pref_switch_items_caching_off">文章不会被保存到设备内存,应用程序在离线时将无法阅读它们</string> <string name="pref_switch_items_caching_off">文章不会被保存到设备内存,应用程序在离线时将无法阅读它们</string>
@ -99,7 +97,7 @@
<string name="pref_switch_periodic_refresh_on">将定期同步文章</string> <string name="pref_switch_periodic_refresh_on">将定期同步文章</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[同步间隔 (>= 15分钟)]]></string> <string name="pref_periodic_refresh_minutes_title"><![CDATA[同步间隔 (>= 15分钟)]]></string>
<string name="pref_switch_refresh_when_charging">仅在手机充电时刷新</string> <string name="pref_switch_refresh_when_charging">仅在手机充电时刷新</string>
<string name="loading_notification_title">加载中...</string> <string name="loading_notification_title">加载中</string>
<string name="loading_notification_text">Selfoss 正在同步您的文章</string> <string name="loading_notification_text">Selfoss 正在同步您的文章</string>
<string name="notification_channel_sync">同步通知</string> <string name="notification_channel_sync">同步通知</string>
<string name="new_items_channel_sync">新条目通知</string> <string name="new_items_channel_sync">新条目通知</string>
@ -122,10 +120,10 @@
<string name="mode_dark">深色模式</string> <string name="mode_dark">深色模式</string>
<string name="mode_system">遵循系统设置</string> <string name="mode_system">遵循系统设置</string>
<string name="mode_light">浅色模式</string> <string name="mode_light">浅色模式</string>
<string name="pref_switch_enable_analytics">Enable analytics</string> <string name="gdpr_dialog_title">该应用不分享任何关于您的个人数据。</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string> <string name="gdpr_dialog_message"><![CDATA[崩溃报告发送现已启用。 可以从设置页面禁用它。 请记住,崩溃报告对于应用程序开发是必需的。]]></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">发生崩溃。请将细节发送给开发人员。</string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string> <string name="pref_switch_disable_acra">"禁用自动错误报告 "</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> <string name="menu_home_filter">筛选器</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>
</resources> </resources>

View File

@ -69,7 +69,6 @@
<string name="pref_header_theme">主题</string> <string name="pref_header_theme">主题</string>
<string name="pref_selfoss_category">塞尔福斯 Api</string> <string name="pref_selfoss_category">塞尔福斯 Api</string>
<string name="pref_api_items_number_title">已加载项目编号</string> <string name="pref_api_items_number_title">已加载项目编号</string>
<string name="pref_hidden_tags">Hidden Tags</string>
<string name="pref_general_infinite_loading_title">翻页时载入更多文章</string> <string name="pref_general_infinite_loading_title">翻页时载入更多文章</string>
<string name="translation">翻译</string> <string name="translation">翻译</string>
<string name="cant_open_invalid_url">项目链接地址无效。我正在设法解决这个问题,以避免应用程序崩溃。</string> <string name="cant_open_invalid_url">项目链接地址无效。我正在设法解决这个问题,以避免应用程序崩溃。</string>
@ -84,7 +83,6 @@
<string name="markall_dialog_message">This will mark all the items as read.</string> <string name="markall_dialog_message">This will mark all the items as read.</string>
<string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string> <string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string>
<string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string> <string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string>
<string name="filter_item_hidden_tags">Hidden Tags</string>
<string name="unmark">Mark item as unread</string> <string name="unmark">Mark item as unread</string>
<string name="pref_header_offline">Offline and cache</string> <string name="pref_header_offline">Offline and cache</string>
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string> <string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
@ -99,7 +97,7 @@
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string> <string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string> <string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string>
<string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string> <string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string>
<string name="loading_notification_title">Loading ...</string> <string name="loading_notification_title">Loading </string>
<string name="loading_notification_text">Selfoss is syncing your articles</string> <string name="loading_notification_text">Selfoss is syncing your articles</string>
<string name="notification_channel_sync">Sync notification</string> <string name="notification_channel_sync">Sync notification</string>
<string name="new_items_channel_sync">New items notification</string> <string name="new_items_channel_sync">New items notification</string>
@ -122,10 +120,10 @@
<string name="mode_dark">Dark mode</string> <string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string> <string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string> <string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string> <string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string> <string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</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>
</resources> </resources>

View File

@ -69,7 +69,6 @@
<string name="pref_header_theme">Themes</string> <string name="pref_header_theme">Themes</string>
<string name="pref_selfoss_category">Selfoss Api</string> <string name="pref_selfoss_category">Selfoss Api</string>
<string name="pref_api_items_number_title">Loaded items number</string> <string name="pref_api_items_number_title">Loaded items number</string>
<string name="pref_hidden_tags">Hidden Tags</string>
<string name="pref_general_infinite_loading_title">Load more articles on scroll</string> <string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Translation</string> <string name="translation">Translation</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string> <string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
@ -84,7 +83,6 @@
<string name="markall_dialog_message">This will mark all the items as read.</string> <string name="markall_dialog_message">This will mark all the items as read.</string>
<string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string> <string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string>
<string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string> <string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string>
<string name="filter_item_hidden_tags">Hidden Tags</string>
<string name="unmark">Mark item as unread</string> <string name="unmark">Mark item as unread</string>
<string name="pref_header_offline">Offline and cache</string> <string name="pref_header_offline">Offline and cache</string>
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string> <string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
@ -107,7 +105,7 @@
<string name="new_items_notification_text">%1$d new items loaded.</string> <string name="new_items_notification_text">%1$d new items loaded.</string>
<string name="pref_switch_notify_new_items">Notify on new items synced.</string> <string name="pref_switch_notify_new_items">Notify on new items synced.</string>
<string name="shortcut_offline">Offline</string> <string name="shortcut_offline">Offline</string>
<string name="pref_api_timeout">Api Timeout</string> <string name="pref_api_timeout">Api Timeout (seconds)</string>
<string name="pref_header_experimental">Experimental</string> <string name="pref_header_experimental">Experimental</string>
<string name="webview_dialog_issue_message">Webview not available. Disabling the article viewer to avoid any future crashes. Will load articles inside of your browser from now on.</string> <string name="webview_dialog_issue_message">Webview not available. Disabling the article viewer to avoid any future crashes. Will load articles inside of your browser from now on.</string>
<string name="webview_dialog_issue_title">Webview issue</string> <string name="webview_dialog_issue_title">Webview issue</string>
@ -125,10 +123,10 @@
<string name="mode_dark">Dark mode</string> <string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string> <string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string> <string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string> <string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string> <string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</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>
</resources> </resources>

View File

@ -14,15 +14,6 @@
android:title="@string/pref_api_items_number_title" android:title="@string/pref_api_items_number_title"
app:iconSpaceReserved="false"/> app:iconSpaceReserved="false"/>
<EditTextPreference
android:defaultValue=""
android:hint="@string/add_source_hint_tags"
android:key="hidden_tags"
android:selectAllOnFocus="true"
android:singleLine="true"
android:title="@string/pref_hidden_tags"
app:iconSpaceReserved="false"/>
<SwitchPreference <SwitchPreference
android:defaultValue="false" android:defaultValue="false"
android:key="infinite_loading" android:key="infinite_loading"

View File

@ -38,13 +38,6 @@
android:icon="@drawable/ic_widgets_black_24dp" /> android:icon="@drawable/ic_widgets_black_24dp" />
<SwitchPreference
android:defaultValue="false"
android:key="enable_analytics"
android:title="@string/pref_switch_enable_analytics"
android:icon="@drawable/ic_baseline_insights_24"/>
<SwitchPreference <SwitchPreference
android:defaultValue="false" android:defaultValue="false"
android:key="acra.disable" android:key="acra.disable"

View File

@ -11,11 +11,14 @@ class DatesTest {
private val v3Date = "2013-04-07T13:43:00+01:00" private val v3Date = "2013-04-07T13:43:00+01:00"
private val v4Date = "2013-04-07 13:43:00" private val v4Date = "2013-04-07 13:43:00"
private val bug1Date = "2022-12-24T17:00:08+00"
@Test @Test
fun v3_date_should_be_parsed() { fun v3_date_should_be_parsed() {
val date = DateUtils.parseDate(v3Date) val date = DateUtils.parseDate(v3Date)
val expected = LocalDateTime(2013, 4, 7, 13, 43, 0, 0).toInstant(TimeZone.of("UTC+1")) .toEpochMilliseconds() val expected =
LocalDateTime(2013, 4, 7, 14, 43, 0, 0).toInstant(TimeZone.currentSystemDefault())
.toEpochMilliseconds()
assertEquals(date, expected) assertEquals(date, expected)
} }
@ -30,4 +33,14 @@ class DatesTest {
assertEquals(date, expected) assertEquals(date, expected)
} }
@Test
fun bug1_date_should_be_parsed() {
val date = DateUtils.parseDate(bug1Date)
val expected =
LocalDateTime(2022, 12, 24, 18, 0, 8, 0).toInstant(TimeZone.currentSystemDefault())
.toEpochMilliseconds()
assertEquals(date, expected)
}
} }

View File

@ -58,6 +58,7 @@ class RepositoryTest {
data = SelfossModel.Stats(NUMBER_ARTICLES, NUMBER_UNREAD, NUMBER_STARRED) data = SelfossModel.Stats(NUMBER_ARTICLES, NUMBER_UNREAD, NUMBER_STARRED)
) )
every { db.itemsQueries.deleteItemsWhereSource(any()) } returns Unit
every { db.itemsQueries.items().executeAsList() } returns generateTestDBItems() every { db.itemsQueries.items().executeAsList() } returns generateTestDBItems()
every { db.tagsQueries.deleteAllTags() } returns Unit every { db.tagsQueries.deleteAllTags() } returns Unit
every { db.tagsQueries.transaction(any(), any()) } returns Unit every { db.tagsQueries.transaction(any(), any()) } returns Unit
@ -369,14 +370,7 @@ class RepositoryTest {
@Test @Test
fun get_tags() { fun get_tags() {
val tags = listOf( val (tags, tagsDB) = prepareTags()
SelfossModel.Tag("test", "red", 6),
SelfossModel.Tag("second", "yellow", 0)
)
val tagsDB = listOf(
TAG("test_DB", "red", 6),
TAG("second_DB", "yellow", 0)
)
coEvery { api.tags() } returns StatusAndData(success = true, data = tags) coEvery { api.tags() } returns StatusAndData(success = true, data = tags)
coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB
@ -396,17 +390,7 @@ class RepositoryTest {
@Test @Test
fun get_tags_with_sources_update_disabled() { fun get_tags_with_sources_update_disabled() {
val tags = listOf( val (tags, tagsDB) = prepareTags()
SelfossModel.Tag("test", "red", 6),
SelfossModel.Tag("second", "yellow", 0)
)
val tagsDB = listOf(
TAG("test_DB", "red", 6),
TAG("second_DB", "yellow", 0)
)
coEvery { api.tags() } returns StatusAndData(success = true, data = tags)
coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB
every { appSettingsService.isUpdateSourcesEnabled() } returns false every { appSettingsService.isUpdateSourcesEnabled() } returns false
every { appSettingsService.isItemCachingEnabled() } returns true every { appSettingsService.isItemCachingEnabled() } returns true
@ -426,17 +410,7 @@ class RepositoryTest {
@Test @Test
fun get_tags_with_items_caching_disabled() { fun get_tags_with_items_caching_disabled() {
val tags = listOf( val (tags, _) = prepareTags()
SelfossModel.Tag("test", "red", 6),
SelfossModel.Tag("second", "yellow", 0)
)
val tagsDB = listOf(
TAG("test_DB", "red", 6),
TAG("second_DB", "yellow", 0)
)
coEvery { api.tags() } returns StatusAndData(success = true, data = tags)
coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB
every { appSettingsService.isUpdateSourcesEnabled() } returns true every { appSettingsService.isUpdateSourcesEnabled() } returns true
every { appSettingsService.isItemCachingEnabled() } returns false every { appSettingsService.isItemCachingEnabled() } returns false
@ -453,17 +427,7 @@ class RepositoryTest {
@Test @Test
fun get_tags_with_sources_update_and_items_caching_disabled() { fun get_tags_with_sources_update_and_items_caching_disabled() {
val tags = listOf( val (tags, tagsDB) = prepareTags()
SelfossModel.Tag("test", "red", 6),
SelfossModel.Tag("second", "yellow", 0)
)
val tagsDB = listOf(
TAG("test_DB", "red", 6),
TAG("second_DB", "yellow", 0)
)
coEvery { api.tags() } returns StatusAndData(success = true, data = tags)
coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB
every { appSettingsService.isUpdateSourcesEnabled() } returns false every { appSettingsService.isUpdateSourcesEnabled() } returns false
every { appSettingsService.isItemCachingEnabled() } returns false every { appSettingsService.isItemCachingEnabled() } returns false
@ -482,17 +446,7 @@ class RepositoryTest {
@Test @Test
fun get_tags_without_connection() { fun get_tags_without_connection() {
val tags = listOf( val (tags, tagsDB) = prepareTags()
SelfossModel.Tag("test", "red", 6),
SelfossModel.Tag("second", "yellow", 0)
)
val tagsDB = listOf(
TAG("test_DB", "red", 6),
TAG("second_DB", "yellow", 0)
)
coEvery { api.tags() } returns StatusAndData(success = true, data = tags)
coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB
every { appSettingsService.isUpdateSourcesEnabled() } returns true every { appSettingsService.isUpdateSourcesEnabled() } returns true
every { appSettingsService.isItemCachingEnabled() } returns true every { appSettingsService.isItemCachingEnabled() } returns true
@ -510,17 +464,7 @@ class RepositoryTest {
@Test @Test
fun get_tags_without_connection_and_items_caching_disabled() { fun get_tags_without_connection_and_items_caching_disabled() {
val tags = listOf( prepareTags()
SelfossModel.Tag("test", "red", 6),
SelfossModel.Tag("second", "yellow", 0)
)
val tagsDB = listOf(
TAG("test_DB", "red", 6),
TAG("second_DB", "yellow", 0)
)
coEvery { api.tags() } returns StatusAndData(success = true, data = tags)
coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB
every { appSettingsService.isItemCachingEnabled() } returns false every { appSettingsService.isItemCachingEnabled() } returns false
every { appSettingsService.isUpdateSourcesEnabled() } returns true every { appSettingsService.isUpdateSourcesEnabled() } returns true
@ -537,17 +481,7 @@ class RepositoryTest {
@Test @Test
fun get_tags_without_connection_and_sources_update_disabled() { fun get_tags_without_connection_and_sources_update_disabled() {
val tags = listOf( val (tags, tagsDB) = prepareTags()
SelfossModel.Tag("test", "red", 6),
SelfossModel.Tag("second", "yellow", 0)
)
val tagsDB = listOf(
TAG("test_DB", "red", 6),
TAG("second_DB", "yellow", 0)
)
coEvery { api.tags() } returns StatusAndData(success = true, data = tags)
coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB
every { appSettingsService.isUpdateSourcesEnabled() } returns false every { appSettingsService.isUpdateSourcesEnabled() } returns false
every { appSettingsService.isItemCachingEnabled() } returns true every { appSettingsService.isItemCachingEnabled() } returns true
@ -565,17 +499,7 @@ class RepositoryTest {
@Test @Test
fun get_tags_without_connection_and_sources_update_and_items_caching_disabled() { fun get_tags_without_connection_and_sources_update_and_items_caching_disabled() {
val tags = listOf( val (_, tagsDB) = prepareTags()
SelfossModel.Tag("test", "red", 6),
SelfossModel.Tag("second", "yellow", 0)
)
val tagsDB = listOf(
TAG("test_DB", "red", 6),
TAG("second_DB", "yellow", 0)
)
coEvery { api.tags() } returns StatusAndData(success = true, data = tags)
coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB
every { appSettingsService.isUpdateSourcesEnabled() } returns false every { appSettingsService.isUpdateSourcesEnabled() } returns false
every { appSettingsService.isItemCachingEnabled() } returns false every { appSettingsService.isItemCachingEnabled() } returns false
@ -590,8 +514,36 @@ class RepositoryTest {
verify(atLeast = 1) { db.tagsQueries.tags().executeAsList() } verify(atLeast = 1) { db.tagsQueries.tags().executeAsList() }
} }
private fun prepareTags(): Pair<List<SelfossModel.Tag>, List<TAG>> {
val tags = listOf(
SelfossModel.Tag("test", "red", 6),
SelfossModel.Tag("second", "yellow", 0)
)
val tagsDB = listOf(
TAG("test_DB", "red", 6),
TAG("second_DB", "yellow", 0)
)
coEvery { api.tags() } returns StatusAndData(success = true, data = tags)
coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB
return Pair(tags, tagsDB)
}
@Test @Test
fun get_sources() { fun get_sources() {
val (sources, sourcesDB) = prepareSources()
initializeRepository()
var testSources: List<SelfossModel.Source>?
runBlocking {
testSources = repository.getSources()
}
assertSame(sources, testSources)
assertNotEquals(sourcesDB.map { it.toView() }, testSources)
coVerify(exactly = 1) { api.sources() }
}
private fun prepareSources(): Pair<ArrayList<SelfossModel.Source>, List<SOURCE>> {
val sources = arrayListOf( val sources = arrayListOf(
SelfossModel.Source( SelfossModel.Source(
1, 1,
@ -631,60 +583,15 @@ class RepositoryTest {
coEvery { api.sources() } returns StatusAndData(success = true, data = sources) coEvery { api.sources() } returns StatusAndData(success = true, data = sources)
every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB
initializeRepository() return Pair(sources, sourcesDB)
var testSources: List<SelfossModel.Source>?
runBlocking {
testSources = repository.getSources()
}
assertSame(sources, testSources)
assertNotEquals(sourcesDB.map { it.toView() }, testSources)
coVerify(exactly = 1) { api.sources() }
} }
@Test @Test
fun get_sources_with_sources_update_disabled() { fun get_sources_with_sources_update_disabled() {
val sources = arrayListOf( val (sources, sourcesDB) = prepareSources()
SelfossModel.Source(
1,
"First source",
listOf("Test", "second"),
"spouts\\rss\\fulltextrss",
"",
"d8c92cdb1ef119ea85c4b9205c879ca7.png"
),
SelfossModel.Source(
2,
"Second DB source",
listOf("second"),
"spouts\\rss\\fulltextrss",
"",
"b3aa8a664d08eb15d6ff1db2fa83e0d9.png"
)
)
val sourcesDB = listOf(
SOURCE(
"1",
"First source",
"Test,second",
"spouts\\rss\\fulltextrss",
"",
"d8c92cdb1ef119ea85c4b9205c879ca7.png"
),
SOURCE(
"2",
"Second source",
"second",
"spouts\\rss\\fulltextrss",
"",
"b3aa8a664d08eb15d6ff1db2fa83e0d9.png"
)
)
every { appSettingsService.isUpdateSourcesEnabled() } returns false every { appSettingsService.isUpdateSourcesEnabled() } returns false
every { appSettingsService.isItemCachingEnabled() } returns true every { appSettingsService.isItemCachingEnabled() } returns true
coEvery { api.sources() } returns StatusAndData(success = true, data = sources)
every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB
initializeRepository() initializeRepository()
var testSources: List<SelfossModel.Source>? var testSources: List<SelfossModel.Source>?
runBlocking { runBlocking {
@ -701,47 +608,10 @@ class RepositoryTest {
@Test @Test
fun get_sources_with_items_caching_disabled() { fun get_sources_with_items_caching_disabled() {
val sources = arrayListOf( val (sources, _) = prepareSources()
SelfossModel.Source(
1,
"First source",
listOf("Test", "second"),
"spouts\\rss\\fulltextrss",
"",
"d8c92cdb1ef119ea85c4b9205c879ca7.png"
),
SelfossModel.Source(
2,
"Second source",
listOf("second"),
"spouts\\rss\\fulltextrss",
"",
"b3aa8a664d08eb15d6ff1db2fa83e0d9.png"
)
)
val sourcesDB = listOf(
SOURCE(
"1",
"First source",
"Test,second",
"spouts\\rss\\fulltextrss",
"",
"d8c92cdb1ef119ea85c4b9205c879ca7.png"
),
SOURCE(
"2",
"Second source",
"second",
"spouts\\rss\\fulltextrss",
"",
"b3aa8a664d08eb15d6ff1db2fa83e0d9.png"
)
)
every { appSettingsService.isUpdateSourcesEnabled() } returns true every { appSettingsService.isUpdateSourcesEnabled() } returns true
every { appSettingsService.isItemCachingEnabled() } returns false every { appSettingsService.isItemCachingEnabled() } returns false
coEvery { api.sources() } returns StatusAndData(success = true, data = sources)
every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB
initializeRepository() initializeRepository()
var testSources: List<SelfossModel.Source>? var testSources: List<SelfossModel.Source>?
runBlocking { runBlocking {
@ -755,47 +625,10 @@ class RepositoryTest {
@Test @Test
fun get_sources_with_sources_update_and_items_caching_disabled() { fun get_sources_with_sources_update_and_items_caching_disabled() {
val sources = arrayListOf( val (sources, _) = prepareSources()
SelfossModel.Source(
1,
"First source",
listOf("Test", "second"),
"spouts\\rss\\fulltextrss",
"",
"d8c92cdb1ef119ea85c4b9205c879ca7.png"
),
SelfossModel.Source(
2,
"Second source",
listOf("second"),
"spouts\\rss\\fulltextrss",
"",
"b3aa8a664d08eb15d6ff1db2fa83e0d9.png"
)
)
val sourcesDB = listOf(
SOURCE(
"1",
"First source",
"Test,second",
"spouts\\rss\\fulltextrss",
"",
"d8c92cdb1ef119ea85c4b9205c879ca7.png"
),
SOURCE(
"2",
"Second source",
"second",
"spouts\\rss\\fulltextrss",
"",
"b3aa8a664d08eb15d6ff1db2fa83e0d9.png"
)
)
every { appSettingsService.isUpdateSourcesEnabled() } returns false every { appSettingsService.isUpdateSourcesEnabled() } returns false
every { appSettingsService.isItemCachingEnabled() } returns false every { appSettingsService.isItemCachingEnabled() } returns false
coEvery { api.sources() } returns StatusAndData(success = true, data = sources)
every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB
initializeRepository() initializeRepository()
var testSources: List<SelfossModel.Source>? var testSources: List<SelfossModel.Source>?
runBlocking { runBlocking {
@ -809,45 +642,7 @@ class RepositoryTest {
@Test @Test
fun get_sources_without_connection() { fun get_sources_without_connection() {
val sources = arrayListOf( val (_, sourcesDB) = prepareSources()
SelfossModel.Source(
1,
"First source",
listOf("Test", "second"),
"spouts\\rss\\fulltextrss",
"",
"d8c92cdb1ef119ea85c4b9205c879ca7.png"
),
SelfossModel.Source(
2,
"Second source",
listOf("second"),
"spouts\\rss\\fulltextrss",
"",
"b3aa8a664d08eb15d6ff1db2fa83e0d9.png"
)
)
val sourcesDB = listOf(
SOURCE(
"1",
"First DB source",
"Test,second",
"spouts\\rss\\fulltextrss",
"",
"d8c92cdb1ef119ea85c4b9205c879ca7.png"
),
SOURCE(
"2",
"Second source",
"second",
"spouts\\rss\\fulltextrss",
"",
"b3aa8a664d08eb15d6ff1db2fa83e0d9.png"
)
)
coEvery { api.sources() } returns StatusAndData(success = true, data = sources)
every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB
initializeRepository(MutableStateFlow(false)) initializeRepository(MutableStateFlow(false))
var testSources: List<SelfossModel.Source>? var testSources: List<SelfossModel.Source>?
runBlocking { runBlocking {
@ -861,47 +656,10 @@ class RepositoryTest {
@Test @Test
fun get_sources_without_connection_and_items_caching_disabled() { fun get_sources_without_connection_and_items_caching_disabled() {
val sources = arrayListOf( prepareSources()
SelfossModel.Source(
1,
"First source",
listOf("Test", "second"),
"spouts\\rss\\fulltextrss",
"",
"d8c92cdb1ef119ea85c4b9205c879ca7.png"
),
SelfossModel.Source(
2,
"Second source",
listOf("second"),
"spouts\\rss\\fulltextrss",
"",
"b3aa8a664d08eb15d6ff1db2fa83e0d9.png"
)
)
val sourcesDB = listOf(
SOURCE(
"1",
"First DB source",
"Test,second",
"spouts\\rss\\fulltextrss",
"",
"d8c92cdb1ef119ea85c4b9205c879ca7.png"
),
SOURCE(
"2",
"Second source",
"second",
"spouts\\rss\\fulltextrss",
"",
"b3aa8a664d08eb15d6ff1db2fa83e0d9.png"
)
)
every { appSettingsService.isItemCachingEnabled() } returns false every { appSettingsService.isItemCachingEnabled() } returns false
every { appSettingsService.isUpdateSourcesEnabled() } returns true every { appSettingsService.isUpdateSourcesEnabled() } returns true
coEvery { api.sources() } returns StatusAndData(success = true, data = sources)
every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB
initializeRepository(MutableStateFlow(false)) initializeRepository(MutableStateFlow(false))
var testSources: List<SelfossModel.Source>? var testSources: List<SelfossModel.Source>?
runBlocking { runBlocking {
@ -915,47 +673,10 @@ class RepositoryTest {
@Test @Test
fun get_sources_without_connection_and_sources_update_disabled() { fun get_sources_without_connection_and_sources_update_disabled() {
val sources = arrayListOf( val (_, sourcesDB) = prepareSources()
SelfossModel.Source(
1,
"First source",
listOf("Test", "second"),
"spouts\\rss\\fulltextrss",
"",
"d8c92cdb1ef119ea85c4b9205c879ca7.png"
),
SelfossModel.Source(
2,
"Second source",
listOf("second"),
"spouts\\rss\\fulltextrss",
"",
"b3aa8a664d08eb15d6ff1db2fa83e0d9.png"
)
)
val sourcesDB = listOf(
SOURCE(
"1",
"First DB source",
"Test,second",
"spouts\\rss\\fulltextrss",
"",
"d8c92cdb1ef119ea85c4b9205c879ca7.png"
),
SOURCE(
"2",
"Second source",
"second",
"spouts\\rss\\fulltextrss",
"",
"b3aa8a664d08eb15d6ff1db2fa83e0d9.png"
)
)
every { appSettingsService.isItemCachingEnabled() } returns true every { appSettingsService.isItemCachingEnabled() } returns true
every { appSettingsService.isUpdateSourcesEnabled() } returns false every { appSettingsService.isUpdateSourcesEnabled() } returns false
coEvery { api.sources() } returns StatusAndData(success = true, data = sources)
every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB
initializeRepository(MutableStateFlow(false)) initializeRepository(MutableStateFlow(false))
var testSources: List<SelfossModel.Source>? var testSources: List<SelfossModel.Source>?
runBlocking { runBlocking {
@ -969,47 +690,10 @@ class RepositoryTest {
@Test @Test
fun get_sources_without_connection_and_items_caching_and_sources_update_disabled() { fun get_sources_without_connection_and_items_caching_and_sources_update_disabled() {
val sources = arrayListOf( val (_, sourcesDB) = prepareSources()
SelfossModel.Source(
1,
"First source",
listOf("Test", "second"),
"spouts\\rss\\fulltextrss",
"",
"d8c92cdb1ef119ea85c4b9205c879ca7.png"
),
SelfossModel.Source(
2,
"Second source",
listOf("second"),
"spouts\\rss\\fulltextrss",
"",
"b3aa8a664d08eb15d6ff1db2fa83e0d9.png"
)
)
val sourcesDB = listOf(
SOURCE(
"1",
"First DB source",
"Test,second",
"spouts\\rss\\fulltextrss",
"",
"d8c92cdb1ef119ea85c4b9205c879ca7.png"
),
SOURCE(
"2",
"Second source",
"second",
"spouts\\rss\\fulltextrss",
"",
"b3aa8a664d08eb15d6ff1db2fa83e0d9.png"
)
)
every { appSettingsService.isItemCachingEnabled() } returns false every { appSettingsService.isItemCachingEnabled() } returns false
every { appSettingsService.isUpdateSourcesEnabled() } returns false every { appSettingsService.isUpdateSourcesEnabled() } returns false
coEvery { api.sources() } returns StatusAndData(success = true, data = sources)
every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB
initializeRepository(MutableStateFlow(false)) initializeRepository(MutableStateFlow(false))
var testSources: List<SelfossModel.Source>? var testSources: List<SelfossModel.Source>?
runBlocking { runBlocking {
@ -1115,10 +799,11 @@ class RepositoryTest {
initializeRepository() initializeRepository()
var response: Boolean var response: Boolean
runBlocking { runBlocking {
response = repository.deleteSource(5) response = repository.deleteSource(5, "src")
} }
coVerify(exactly = 1) { api.deleteSource(5) } coVerify(exactly = 1) { api.deleteSource(5) }
coVerify(exactly = 1) { db.itemsQueries.deleteItemsWhereSource("src") }
assertSame(true, response) assertSame(true, response)
} }
@ -1129,10 +814,11 @@ class RepositoryTest {
initializeRepository() initializeRepository()
var response: Boolean var response: Boolean
runBlocking { runBlocking {
response = repository.deleteSource(5) response = repository.deleteSource(5, "src")
} }
coVerify(exactly = 1) { api.deleteSource(5) } coVerify(exactly = 1) { api.deleteSource(5) }
coVerify(exactly = 0) { db.itemsQueries.deleteItemsWhereSource("src") }
assertSame(false, response) assertSame(false, response)
} }
@ -1143,10 +829,11 @@ class RepositoryTest {
initializeRepository(MutableStateFlow(false)) initializeRepository(MutableStateFlow(false))
var response: Boolean var response: Boolean
runBlocking { runBlocking {
response = repository.deleteSource(5) response = repository.deleteSource(5, "src")
} }
coVerify(exactly = 0) { api.deleteSource(5) } coVerify(exactly = 0) { api.deleteSource(5) }
coVerify(exactly = 1) { db.itemsQueries.deleteItemsWhereSource("src") }
assertSame(false, response) assertSame(false, response)
} }
@ -1302,16 +989,7 @@ class RepositoryTest {
) )
initializeRepository() initializeRepository()
repository.setTagFilter(SelfossModel.Tag("Tag", "read", 0)) prepareSearch()
repository.setSourceFilter(SelfossModel.Source(
1,
"First source",
listOf("Test", "second"),
"spouts\\rss\\fulltextrss",
"",
"d8c92cdb1ef119ea85c4b9205c879ca7.png"
))
repository.searchFilter = "search"
runBlocking { runBlocking {
repository.tryToCacheItemsAndGetNewOnes() repository.tryToCacheItemsAndGetNewOnes()
} }
@ -1325,16 +1003,7 @@ class RepositoryTest {
StatusAndData(success = false, data = generateTestApiItem()) StatusAndData(success = false, data = generateTestApiItem())
initializeRepository() initializeRepository()
repository.setTagFilter(SelfossModel.Tag("Tag", "read", 0)) prepareSearch()
repository.setSourceFilter(SelfossModel.Source(
1,
"First source",
listOf("Test", "second"),
"spouts\\rss\\fulltextrss",
"",
"d8c92cdb1ef119ea85c4b9205c879ca7.png"
))
repository.searchFilter = "search"
runBlocking { runBlocking {
repository.tryToCacheItemsAndGetNewOnes() repository.tryToCacheItemsAndGetNewOnes()
} }
@ -1348,20 +1017,26 @@ class RepositoryTest {
StatusAndData(success = false, data = generateTestApiItem()) StatusAndData(success = false, data = generateTestApiItem())
initializeRepository(MutableStateFlow(false)) initializeRepository(MutableStateFlow(false))
repository.setTagFilter(SelfossModel.Tag("Tag", "read", 0)) prepareSearch()
repository.setSourceFilter(SelfossModel.Source(
1,
"First source",
listOf("Test", "second"),
"spouts\\rss\\fulltextrss",
"",
"d8c92cdb1ef119ea85c4b9205c879ca7.png"
))
repository.searchFilter = "search"
runBlocking { runBlocking {
repository.tryToCacheItemsAndGetNewOnes() repository.tryToCacheItemsAndGetNewOnes()
} }
coVerify(exactly = 0) { api.getItems(any(), 0, null, null, null, null, 200) } coVerify(exactly = 0) { api.getItems(any(), 0, null, null, null, null, 200) }
} }
private fun prepareSearch() {
repository.setTagFilter(SelfossModel.Tag("Tag", "read", 0))
repository.setSourceFilter(
SelfossModel.Source(
1,
"First source",
listOf("Test", "second"),
"spouts\\rss\\fulltextrss",
"",
"d8c92cdb1ef119ea85c4b9205c879ca7.png"
)
)
repository.searchFilter = "search"
}
} }

View File

@ -17,7 +17,8 @@ fun generateTestDBItems(item: FakeItemParameters = FakeItemParameters()): List<I
icon = item.icon, icon = item.icon,
link = item.link, link = item.link,
sourcetitle = item.sourcetitle, sourcetitle = item.sourcetitle,
tags = item.tags tags = item.tags,
author = item.author
) )
) )
} }
@ -35,7 +36,8 @@ fun generateTestApiItem(item: FakeItemParameters = FakeItemParameters()): List<S
icon = item.icon, icon = item.icon,
link = item.link, link = item.link,
sourcetitle = item.sourcetitle, sourcetitle = item.sourcetitle,
tags = item.tags.split(',') tags = item.tags.split(','),
author = item.author
) )
) )
} }
@ -54,4 +56,5 @@ class FakeItemParameters {
"https://ilblogdellasci.wordpress.com/2022/09/09/etica-della-ricerca-sotto-i-riflettori/" "https://ilblogdellasci.wordpress.com/2022/09/09/etica-della-ricerca-sotto-i-riflettori/"
var sourcetitle = "La Chimica e la Società" var sourcetitle = "La Chimica e la Società"
var tags = "Chimica, Testing" var tags = "Chimica, Testing"
var author = "Someone important"
} }

View File

@ -11,18 +11,18 @@ plugins {
id("com.android.library").version("7.3.1").apply(false) id("com.android.library").version("7.3.1").apply(false)
kotlin("android").version("1.7.20").apply(false) kotlin("android").version("1.7.20").apply(false)
kotlin("multiplatform").version("1.7.20").apply(false) kotlin("multiplatform").version("1.7.20").apply(false)
id("org.sonarqube").version("3.4.0.2513").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"
} }
apply(plugin = "org.sonarqube")
allprojects { allprojects {
repositories { repositories {
google() maven { url = uri("https://nexus.amine-louveau.fr/repository/maven-public/")}
mavenCentral() // IMPORTANT : Add back when new library added
jcenter() // google()
maven { url = uri("https://www.jitpack.io") } // mavenCentral()
// jcenter()
// maven { url = uri("https://www.jitpack.io") }
} }
} }
@ -30,3 +30,7 @@ allprojects {
tasks.register("clean", Delete::class) { tasks.register("clean", Delete::class) {
delete(rootProject.buildDir) delete(rootProject.buildDir)
} }
koverMerged {
enable()
}

View File

@ -2,16 +2,20 @@ val pushCache: String by settings
pluginManagement { pluginManagement {
repositories { repositories {
google() maven { url = uri("https://nexus.amine-louveau.fr/repository/maven-public/")}
gradlePluginPortal() // IMPORTANT : Add back when new plugin added
mavenCentral() // google()
// gradlePluginPortal()
// mavenCentral()
} }
} }
dependencyResolutionManagement { dependencyResolutionManagement {
repositories { repositories {
google() maven { url = uri("https://nexus.amine-louveau.fr/repository/maven-public/")}
mavenCentral() // IMPORTANT : Add back when new library added
// google()
// mavenCentral()
} }
} }

View File

@ -10,6 +10,7 @@ plugins {
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.4.10"
id("org.jetbrains.kotlinx.kover") version "0.6.1"
} }
kotlin { kotlin {

View File

@ -10,7 +10,11 @@ actual class DateUtils {
return try { return try {
Instant.parse(dateString).toEpochMilliseconds() Instant.parse(dateString).toEpochMilliseconds()
} catch (e: Exception) { } catch (e: Exception) {
LocalDateTime.parse(dateString.replace(" ", "T")).toInstant(TimeZone.currentSystemDefault()).toEpochMilliseconds() var str = dateString.replace(" ", "T")
if (str.matches("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\+\\d{2}".toRegex())) {
str = str.split("+")[0]
}
LocalDateTime.parse(str).toInstant(TimeZone.currentSystemDefault()).toEpochMilliseconds()
} }
} }

View File

@ -1,15 +1,6 @@
package bou.amine.apps.readerforselfossv2.model package bou.amine.apps.readerforselfossv2.model
import bou.amine.apps.readerforselfossv2.utils.DateUtils
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.*
class MercuryModel { class MercuryModel {
@ -20,138 +11,4 @@ class MercuryModel {
val lead_image_url: String?, val lead_image_url: String?,
val url: String val url: String
) )
@Serializable
data class Tag(
val tag: String,
val color: String,
val unread: Int
)
@Serializable
class Stats(
val total: Int,
val unread: Int,
val starred: Int
)
@Serializable
data class Spout(
val name: String,
val description: String
)
@Serializable
data class ApiVersion(
val version: String?,
val apiversion: String?
) {
fun getApiMajorVersion() : Int {
var versionNumber = 0
if (apiversion != null) {
versionNumber = apiversion.substringBefore(".").toInt()
}
return versionNumber
}
}
@Serializable
data class Source(
val id: Int,
val title: String,
@Serializable(with = TagsListSerializer::class)
val tags: List<String>,
val spout: String,
val error: String,
val icon: String?
)
@Serializable
data class Item(
val id: Int,
val datetime: String,
val title: String,
val content: String,
@Serializable(with = BooleanSerializer::class)
var unread: Boolean,
@Serializable(with = BooleanSerializer::class)
var starred: Boolean,
val thumbnail: String?,
val icon: String?,
val link: String,
val sourcetitle: String,
@Serializable(with = TagsListSerializer::class)
val tags: List<String>
) {
// TODO: maybe find a better way to handle these kind of urls
fun getLinkDecoded(): String {
var stringUrl: String
stringUrl =
if (link.startsWith("http://news.google.com/news/") || link.startsWith("https://news.google.com/news/")) {
if (link.contains("&amp;url=")) {
link.substringAfter("&amp;url=")
} else {
this.link.replace("&amp;", "&")
}
} else {
this.link.replace("&amp;", "&")
}
// handle :443 => https
if (stringUrl.contains(":443")) {
stringUrl = stringUrl.replace(":443", "").replace("http://", "https://")
}
// handle url not starting with http
if (stringUrl.startsWith("//")) {
stringUrl = "http:$stringUrl"
}
return stringUrl
}
fun sourceAndDateText(): String =
this.sourcetitle.getHtmlDecoded() + DateUtils.parseRelativeDate(this.datetime)
fun toggleStar(): Item {
this.starred = !this.starred
return this
}
}
// TODO: this seems to be super slow.
object TagsListSerializer : KSerializer<List<String>> {
override fun deserialize(decoder: Decoder): List<String> {
return when(val json = ((decoder as JsonDecoder).decodeJsonElement())) {
is JsonArray -> json.toList().map { it.toString() }
else -> json.toString().split(",")
}
}
override val descriptor: SerialDescriptor
get() = PrimitiveSerialDescriptor("tags", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: List<String>) {
TODO("Not yet implemented")
}
}
object BooleanSerializer : KSerializer<Boolean> {
override fun deserialize(decoder: Decoder): Boolean {
val json = ((decoder as JsonDecoder).decodeJsonElement()).jsonPrimitive
return if (json.booleanOrNull != null) {
json.boolean
} else {
json.int == 1
}
}
override val descriptor: SerialDescriptor
get() = PrimitiveSerialDescriptor("b", PrimitiveKind.BOOLEAN)
override fun serialize(encoder: Encoder, value: Boolean) {
TODO("Not yet implemented")
}
}
} }

View File

@ -1,8 +1,5 @@
package bou.amine.apps.readerforselfossv2.model package bou.amine.apps.readerforselfossv2.model
import io.ktor.client.call.*
import io.ktor.client.statement.*
import io.ktor.http.*
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@Serializable @Serializable
@ -21,28 +18,4 @@ class StatusAndData<T>(val success: Boolean, val data: T? = null) {
return StatusAndData(false) return StatusAndData(false)
} }
} }
}
suspend fun responseOrSuccessIf404(r: HttpResponse): SuccessResponse {
return if (r.status === HttpStatusCode.NotFound) {
SuccessResponse(true)
} else {
maybeResponse(r)
}
}
suspend fun maybeResponse(r: HttpResponse): SuccessResponse {
return if (r.status.isSuccess()) {
r.body()
} else {
SuccessResponse(false)
}
}
suspend inline fun <reified T> bodyOrFailure(r: HttpResponse): StatusAndData<T> {
return if (r.status.isSuccess()) {
StatusAndData.succes(r.body())
} else {
StatusAndData.error()
}
} }

View File

@ -57,7 +57,6 @@ class SelfossModel {
val error: String, val error: String,
val icon: String? val icon: String?
) )
@Serializable @Serializable
data class Item( data class Item(
val id: Int, val id: Int,
@ -73,7 +72,8 @@ class SelfossModel {
val link: String, val link: String,
val sourcetitle: String, val sourcetitle: String,
@Serializable(with = TagsListSerializer::class) @Serializable(with = TagsListSerializer::class)
val tags: List<String> val tags: List<String>,
val author: String?
) { ) {
// TODO: maybe find a better way to handle these kind of urls // TODO: maybe find a better way to handle these kind of urls
fun getLinkDecoded(): String { fun getLinkDecoded(): String {
@ -102,8 +102,14 @@ class SelfossModel {
return stringUrl return stringUrl
} }
fun sourceAndDateText(): String = fun sourceAuthorAndDate(): String {
this.sourcetitle.getHtmlDecoded() + DateUtils.parseRelativeDate(this.datetime) var txt = this.sourcetitle.getHtmlDecoded()
if (!this.author.isNullOrBlank()) {
txt += " (by ${this.author}) "
}
txt += DateUtils.parseRelativeDate(this.datetime)
return txt
}
fun toggleStar(): Item { fun toggleStar(): Item {
this.starred = !this.starred this.starred = !this.starred
@ -111,6 +117,7 @@ class SelfossModel {
} }
} }
// TODO: this seems to be super slow. // TODO: this seems to be super slow.
object TagsListSerializer : KSerializer<List<String>> { object TagsListSerializer : KSerializer<List<String>> {
override fun deserialize(decoder: Decoder): List<String> { override fun deserialize(decoder: Decoder): List<String> {

View File

@ -8,13 +8,19 @@ import bou.amine.apps.readerforselfossv2.rest.SelfossApi
import bou.amine.apps.readerforselfossv2.service.AppSettingsService import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import bou.amine.apps.readerforselfossv2.utils.* import bou.amine.apps.readerforselfossv2.utils.*
import io.github.aakira.napier.Napier import io.github.aakira.napier.Napier
import io.ktor.client.call.*
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class Repository(private val api: SelfossApi, private val appSettingsService: AppSettingsService, val isConnectionAvailable: MutableStateFlow<Boolean>, private val db: ReaderForSelfossDB) { class Repository(
private val api: SelfossApi,
private val appSettingsService: AppSettingsService,
val isConnectionAvailable: MutableStateFlow<Boolean>,
private val db: ReaderForSelfossDB
) {
var items = ArrayList<SelfossModel.Item>() var items = ArrayList<SelfossModel.Item>()
var connectionMonitored = false var connectionMonitored = false
@ -41,6 +47,8 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
private var fetchedSources = false private var fetchedSources = false
private var fetchedTags = false private var fetchedTags = false
private var _readerItems = ArrayList<SelfossModel.Item>()
suspend fun getNewerItems(): ArrayList<SelfossModel.Item> { suspend fun getNewerItems(): ArrayList<SelfossModel.Item> {
// TODO: Use the updatedSince parameter // TODO: Use the updatedSince parameter
var fetchedItems: StatusAndData<List<SelfossModel.Item>> = StatusAndData.error() var fetchedItems: StatusAndData<List<SelfossModel.Item>> = StatusAndData.error()
@ -146,7 +154,8 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
} }
suspend fun getTags(): List<SelfossModel.Tag> { suspend fun getTags(): List<SelfossModel.Tag> {
val isDatabaseEnabled = appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled() val isDatabaseEnabled =
appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled()
return if (isNetworkAvailable() && !fetchedTags) { return if (isNetworkAvailable() && !fetchedTags) {
val apiTags = api.tags() val apiTags = api.tags()
if (apiTags.success && apiTags.data != null && isDatabaseEnabled) { if (apiTags.success && apiTags.data != null && isDatabaseEnabled) {
@ -178,7 +187,8 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
} }
suspend fun getSources(): ArrayList<SelfossModel.Source> { suspend fun getSources(): ArrayList<SelfossModel.Source> {
val isDatabaseEnabled = appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled() val isDatabaseEnabled =
appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled()
return if (isNetworkAvailable() && !fetchedSources) { return if (isNetworkAvailable() && !fetchedSources) {
val apiSources = api.sources() val apiSources = api.sources()
if (apiSources.success && apiSources.data != null && isDatabaseEnabled) { if (apiSources.success && apiSources.data != null && isDatabaseEnabled) {
@ -349,13 +359,20 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
return response return response
} }
suspend fun deleteSource(id: Int): Boolean { suspend fun deleteSource(id: Int, title: String): Boolean {
var success = false var success = false
if (isNetworkAvailable()) { if (isNetworkAvailable()) {
val response = api.deleteSource(id) val response = api.deleteSource(id)
success = response.isSuccess success = response.isSuccess
} }
// We filter on success or if the network isn't available
if (success || !isNetworkAvailable()) {
items = ArrayList(items.filter { it.sourcetitle != title })
setReaderItems(items)
db.itemsQueries.deleteItemsWhereSource(title)
}
return success return success
} }
@ -380,18 +397,35 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
return result return result
} }
suspend fun shouldBeSelfossInstance(): Pair<Boolean, Boolean> {
var fetchFailed = true
var showSelfossOnlyModal = false
if (isNetworkAvailable()) {
try {
// Trying to fetch one item, and check someone is trying to use the app with
// a random rss feed, that would throw a NoTransformationFoundException
fetchFailed = !api.getItemsWithoutCatch().success
} catch (e: NoTransformationFoundException) {
showSelfossOnlyModal = true
} catch (e: Throwable) {
Napier.e(e.stackTraceToString(), tag = "RepositoryImpl.shouldBeSelfossInstance")
}
}
return Pair(fetchFailed, showSelfossOnlyModal)
}
suspend fun logout() { suspend fun logout() {
if (isNetworkAvailable()) { if (isNetworkAvailable()) {
try { try {
val response = api.logout() val response = api.logout()
if (response.isSuccess) { if (!response.isSuccess) {
Napier.e("Couldn't logout.", tag = "RepositoryImpl.logout") Napier.e("Couldn't logout.", tag = "RepositoryImpl.logout")
} }
} catch (cause: Throwable) { } catch (cause: Throwable) {
Napier.e(cause.stackTraceToString(), tag = "RepositoryImpl.logout") Napier.e(cause.stackTraceToString(), tag = "RepositoryImpl.logout")
} finally {
appSettingsService.clearAll()
} }
appSettingsService.clearAll()
} else { } else {
appSettingsService.clearAll() appSettingsService.clearAll()
} }
@ -456,11 +490,30 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
private fun getDBItems(): List<ITEM> = db.itemsQueries.items().executeAsList() private fun getDBItems(): List<ITEM> = db.itemsQueries.items().executeAsList()
private fun insertDBAction(articleid: String, read: Boolean = false, unread: Boolean = false, starred: Boolean = false, unstarred: Boolean = false) = private fun insertDBAction(
articleid: String,
read: Boolean = false,
unread: Boolean = false,
starred: Boolean = false,
unstarred: Boolean = false
) =
db.actionsQueries.insertAction(articleid, read, unread, starred, unstarred) db.actionsQueries.insertAction(articleid, read, unread, starred, unstarred)
private fun updateDBItem(item: SelfossModel.Item) = private fun updateDBItem(item: SelfossModel.Item) =
db.itemsQueries.updateItem(item.datetime, item.title.getHtmlDecoded(), item.content, item.unread, item.starred, item.thumbnail, item.icon, item.link, item.sourcetitle, item.tags.joinToString(","), item.id.toString()) db.itemsQueries.updateItem(
item.datetime,
item.title.getHtmlDecoded(),
item.content,
item.unread,
item.starred,
item.thumbnail,
item.icon,
item.link,
item.sourcetitle,
item.tags.joinToString(","),
item.author,
item.id.toString()
)
suspend fun tryToCacheItemsAndGetNewOnes(): List<SelfossModel.Item> { suspend fun tryToCacheItemsAndGetNewOnes(): List<SelfossModel.Item> {
try { try {
@ -515,4 +568,16 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
fun setSourceFilter(source: SelfossModel.Source?) { fun setSourceFilter(source: SelfossModel.Source?) {
_sourceFilter.value = source _sourceFilter.value = source
} }
fun setReaderItems(readerItems: ArrayList<SelfossModel.Item>) {
_readerItems = readerItems
}
fun getReaderItems(): ArrayList<SelfossModel.Item> {
return _readerItems
}
fun migrate(driverFactory: DriverFactory) {
ReaderForSelfossDB.Schema.migrate(driverFactory.createDriver(), 0, 1)
}
} }

View File

@ -0,0 +1,79 @@
package bou.amine.apps.readerforselfossv2.rest
import bou.amine.apps.readerforselfossv2.model.StatusAndData
import bou.amine.apps.readerforselfossv2.model.SuccessResponse
import io.github.aakira.napier.Napier
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.request.*
import io.ktor.client.request.forms.*
import io.ktor.client.statement.*
import io.ktor.http.*
suspend fun responseOrSuccessIf404(r: HttpResponse?): SuccessResponse {
return if (r != null && r.status === HttpStatusCode.NotFound) {
SuccessResponse(true)
} else {
maybeResponse(r)
}
}
suspend fun maybeResponse(r: HttpResponse?): SuccessResponse {
return if (r != null && r.status.isSuccess()) {
r.body()
} else {
SuccessResponse(false)
}
}
suspend inline fun <reified T> bodyOrFailure(r: HttpResponse?): StatusAndData<T> {
return if (r != null && r.status.isSuccess()) {
StatusAndData.succes(r.body())
} else {
StatusAndData.error()
}
}
inline fun tryToRequest(
requestType: String,
fn: () -> HttpResponse
): HttpResponse? {
var response: HttpResponse? = null
try {
response = fn()
} catch (ex: Exception) {
Napier.e("Couldn't execute $requestType request", ex, "tryTo$requestType")
}
return response
}
suspend inline fun HttpClient.tryToGet(
urlString: String,
crossinline block: HttpRequestBuilder.() -> Unit = {}
): HttpResponse? = tryToRequest("Get") { return this.get { url(urlString); block() } }
suspend inline fun HttpClient.tryToPost(
urlString: String,
block: HttpRequestBuilder.() -> Unit = {}
): HttpResponse? = tryToRequest("Post") { return this.post { url(urlString); block() } }
suspend inline fun HttpClient.tryToDelete(
urlString: String,
block: HttpRequestBuilder.() -> Unit = {}
): HttpResponse? = tryToRequest("Delete") { return this.delete { url(urlString); block() } }
suspend fun HttpClient.tryToSubmitForm(
url: String,
formParameters: Parameters = Parameters.Empty,
encodeInQuery: Boolean = false,
block: HttpRequestBuilder.() -> Unit = {}
): HttpResponse? =
tryToRequest("SubmitForm") {
return this.submitForm(formParameters, encodeInQuery) {
url(url)
block()
}
}

View File

@ -1,6 +1,8 @@
package bou.amine.apps.readerforselfossv2.rest package bou.amine.apps.readerforselfossv2.rest
import bou.amine.apps.readerforselfossv2.model.* import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.model.StatusAndData
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.*
@ -10,7 +12,6 @@ import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.plugins.cookies.* import io.ktor.client.plugins.cookies.*
import io.ktor.client.plugins.logging.* import io.ktor.client.plugins.logging.*
import io.ktor.client.request.* import io.ktor.client.request.*
import io.ktor.client.request.forms.*
import io.ktor.client.statement.* import io.ktor.client.statement.*
import io.ktor.http.* import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.* import io.ktor.serialization.kotlinx.json.*
@ -50,7 +51,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
retryIf { _, response -> retryIf { _, response ->
response.status == HttpStatusCode.Forbidden && shouldHavePostLogin() && hasLoginInfo() response.status == HttpStatusCode.Forbidden && shouldHavePostLogin() && hasLoginInfo()
} }
modifyRequest { _ -> modifyRequest {
Napier.i("Will modify", tag = "HttpSend") Napier.i("Will modify", tag = "HttpSend")
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
Napier.i("Will login", tag = "HttpSend") Napier.i("Will login", tag = "HttpSend")
@ -75,7 +76,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
// Api version was introduces after the POST login, so when there is a version, it should be available // Api version was introduces after the POST login, so when there is a version, it should be available
private fun shouldHavePostLogin() = appSettingsService.getApiVersion() != -1 private fun shouldHavePostLogin() = appSettingsService.getApiVersion() != -1
private fun hasLoginInfo() = appSettingsService.getUserName() != null && appSettingsService.getPassword() != null private fun hasLoginInfo() = appSettingsService.getUserName().isNotEmpty() && appSettingsService.getPassword().isNotEmpty()
suspend fun login(): SuccessResponse = suspend fun login(): SuccessResponse =
if (appSettingsService.getUserName().isNotEmpty() && appSettingsService.getPassword().isNotEmpty()) { if (appSettingsService.getUserName().isNotEmpty() && appSettingsService.getPassword().isNotEmpty()) {
@ -88,12 +89,12 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
SuccessResponse(true) SuccessResponse(true)
} }
private suspend fun getLogin() = maybeResponse(client.get(url("/login")) { private suspend fun getLogin() = maybeResponse(client.tryToGet(url("/login")) {
parameter("username", appSettingsService.getUserName()) parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword()) parameter("password", appSettingsService.getPassword())
}) })
private suspend fun postLogin() = maybeResponse(client.post(url("/login")) { private suspend fun postLogin() = maybeResponse(client.tryToPost(url("/login")) {
parameter("username", appSettingsService.getUserName()) parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword()) parameter("password", appSettingsService.getPassword())
}) })
@ -106,9 +107,9 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
maybeLogoutIfAvailable() maybeLogoutIfAvailable()
} }
private suspend fun maybeLogoutIfAvailable() = responseOrSuccessIf404(client.get(url("/logout"))) private suspend fun maybeLogoutIfAvailable() = responseOrSuccessIf404(client.tryToGet(url("/logout")))
private suspend fun doLogout() = maybeResponse(client.delete(url("/api/session/current"))) private suspend fun doLogout() = maybeResponse(client.tryToDelete(url("/api/session/current")))
suspend fun getItems( suspend fun getItems(
type: String, type: String,
@ -119,7 +120,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
updatedSince: String?, updatedSince: String?,
items: Int? = null items: Int? = null
): StatusAndData<List<SelfossModel.Item>> = ): StatusAndData<List<SelfossModel.Item>> =
bodyOrFailure(client.get(url("/items")) { bodyOrFailure(client.tryToGet(url("/items")) {
if (!shouldHavePostLogin()) { if (!shouldHavePostLogin()) {
parameter("username", appSettingsService.getUserName()) parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword()) parameter("password", appSettingsService.getPassword())
@ -133,8 +134,18 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
parameter("offset", offset) parameter("offset", offset)
}) })
suspend fun getItemsWithoutCatch(): StatusAndData<List<SelfossModel.Item>> =
bodyOrFailure(client.get(url("/items")) {
if (!shouldHavePostLogin()) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}
parameter("type", "all")
parameter("items", 1)
})
suspend fun stats(): StatusAndData<SelfossModel.Stats> = suspend fun stats(): StatusAndData<SelfossModel.Stats> =
bodyOrFailure(client.get(url("/stats")) { bodyOrFailure(client.tryToGet(url("/stats")) {
if (!shouldHavePostLogin()) { if (!shouldHavePostLogin()) {
parameter("username", appSettingsService.getUserName()) parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword()) parameter("password", appSettingsService.getPassword())
@ -142,7 +153,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
}) })
suspend fun tags(): StatusAndData<List<SelfossModel.Tag>> = suspend fun tags(): StatusAndData<List<SelfossModel.Tag>> =
bodyOrFailure(client.get(url("/tags")) { bodyOrFailure(client.tryToGet(url("/tags")) {
if (!shouldHavePostLogin()) { if (!shouldHavePostLogin()) {
parameter("username", appSettingsService.getUserName()) parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword()) parameter("password", appSettingsService.getPassword())
@ -150,7 +161,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
}) })
suspend fun update(): StatusAndData<String> = suspend fun update(): StatusAndData<String> =
bodyOrFailure(client.get(url("/update")) { bodyOrFailure(client.tryToGet(url("/update")) {
if (!shouldHavePostLogin()) { if (!shouldHavePostLogin()) {
parameter("username", appSettingsService.getUserName()) parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword()) parameter("password", appSettingsService.getPassword())
@ -158,7 +169,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
}) })
suspend fun spouts(): StatusAndData<Map<String, SelfossModel.Spout>> = suspend fun spouts(): StatusAndData<Map<String, SelfossModel.Spout>> =
bodyOrFailure(client.get(url("/sources/spouts")) { bodyOrFailure(client.tryToGet(url("/sources/spouts")) {
if (!shouldHavePostLogin()) { if (!shouldHavePostLogin()) {
parameter("username", appSettingsService.getUserName()) parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword()) parameter("password", appSettingsService.getPassword())
@ -166,7 +177,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
}) })
suspend fun sources(): StatusAndData<ArrayList<SelfossModel.Source>> = suspend fun sources(): StatusAndData<ArrayList<SelfossModel.Source>> =
bodyOrFailure(client.get(url("/sources/list")) { bodyOrFailure(client.tryToGet(url("/sources/list")) {
if (!shouldHavePostLogin()) { if (!shouldHavePostLogin()) {
parameter("username", appSettingsService.getUserName()) parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword()) parameter("password", appSettingsService.getPassword())
@ -174,10 +185,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
}) })
suspend fun version(): StatusAndData<SelfossModel.ApiVersion> = suspend fun version(): StatusAndData<SelfossModel.ApiVersion> =
bodyOrFailure(client.get(url("/api/about"))) bodyOrFailure(client.tryToGet(url("/api/about")))
suspend fun markAsRead(id: String): SuccessResponse = suspend fun markAsRead(id: String): SuccessResponse =
maybeResponse(client.post(url("/mark/$id")) { maybeResponse(client.tryToPost(url("/mark/$id")) {
if (!shouldHavePostLogin()) { if (!shouldHavePostLogin()) {
parameter("username", appSettingsService.getUserName()) parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword()) parameter("password", appSettingsService.getPassword())
@ -185,7 +196,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
}) })
suspend fun unmarkAsRead(id: String): SuccessResponse = suspend fun unmarkAsRead(id: String): SuccessResponse =
maybeResponse(client.post(url("/unmark/$id")) { maybeResponse(client.tryToPost(url("/unmark/$id")) {
if (!shouldHavePostLogin()) { if (!shouldHavePostLogin()) {
parameter("username", appSettingsService.getUserName()) parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword()) parameter("password", appSettingsService.getPassword())
@ -193,7 +204,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
}) })
suspend fun starr(id: String): SuccessResponse = suspend fun starr(id: String): SuccessResponse =
maybeResponse(client.post(url("/starr/$id")) { maybeResponse(client.tryToPost(url("/starr/$id")) {
if (!shouldHavePostLogin()) { if (!shouldHavePostLogin()) {
parameter("username", appSettingsService.getUserName()) parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword()) parameter("password", appSettingsService.getPassword())
@ -201,7 +212,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
}) })
suspend fun unstarr(id: String): SuccessResponse = suspend fun unstarr(id: String): SuccessResponse =
maybeResponse(client.post(url("/unstarr/$id")) { maybeResponse(client.tryToPost(url("/unstarr/$id")) {
if (!shouldHavePostLogin()) { if (!shouldHavePostLogin()) {
parameter("username", appSettingsService.getUserName()) parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword()) parameter("password", appSettingsService.getPassword())
@ -209,7 +220,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
}) })
suspend fun markAllAsRead(ids: List<String>): SuccessResponse = suspend fun markAllAsRead(ids: List<String>): SuccessResponse =
maybeResponse(client.submitForm( maybeResponse(client.tryToSubmitForm(
url = url("/mark"), url = url("/mark"),
formParameters = Parameters.build { formParameters = Parameters.build {
if (!shouldHavePostLogin()) { if (!shouldHavePostLogin()) {
@ -229,43 +240,21 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
): SuccessResponse = ): SuccessResponse =
maybeResponse( maybeResponse(
if (appSettingsService.getApiVersion() > 1) { if (appSettingsService.getApiVersion() > 1) {
createSource2(title, url, spout, tags, filter) createSource("tags[]", title, url, spout, tags, filter)
} else { } else {
createSource(title, url, spout, tags, filter) createSource("tags", title, url, spout, tags, filter)
} }
) )
private suspend fun createSource( private suspend fun createSource(
tagsParamName: String,
title: String, title: String,
url: String, url: String,
spout: String, spout: String,
tags: String, tags: String,
filter: String filter: String
): HttpResponse = ): HttpResponse? =
client.submitForm( client.tryToSubmitForm(
url = url("/source"),
formParameters = Parameters.build {
// TODO: test this
if (!shouldHavePostLogin()) {
append("username", appSettingsService.getUserName())
append("password", appSettingsService.getPassword())
}
append("title", title)
append("url", url)
append("spout", spout)
append("tags", tags)
append("filter", filter)
}
)
private suspend fun createSource2(
title: String,
url: String,
spout: String,
tags: String,
filter: String
): HttpResponse =
client.submitForm(
url = url("/source"), url = url("/source"),
formParameters = Parameters.build { formParameters = Parameters.build {
if (!shouldHavePostLogin()) { if (!shouldHavePostLogin()) {
@ -275,13 +264,13 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
append("title", title) append("title", title)
append("url", url) append("url", url)
append("spout", spout) append("spout", spout)
append("tags[]", tags) append(tagsParamName, tags)
append("filter", filter) append("filter", filter)
} }
) )
suspend fun deleteSource(id: Int): SuccessResponse = suspend fun deleteSource(id: Int): SuccessResponse =
maybeResponse(client.delete(url("/source/$id")) { maybeResponse(client.tryToDelete(url("/source/$id")) {
if (!shouldHavePostLogin()) { if (!shouldHavePostLogin()) {
parameter("username", appSettingsService.getUserName()) parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword()) parameter("password", appSettingsService.getPassword())

View File

@ -27,7 +27,6 @@ class AppSettingsService {
private var _notifyNewItems: Boolean? = null private var _notifyNewItems: Boolean? = null
private var _itemsNumber: Int? = null private var _itemsNumber: Int? = null
private var _apiTimeout: Long? = null private var _apiTimeout: Long? = null
private var _hiddenTags: List<String>? = null
private var _refreshMinutes: Long = 360 private var _refreshMinutes: Long = 360
private var _markOnScroll: Boolean? = null private var _markOnScroll: Boolean? = null
private var _activeAlignment: Int? = null private var _activeAlignment: Int? = null
@ -36,7 +35,6 @@ class AppSettingsService {
private var _staticBar: Boolean? = null private var _staticBar: Boolean? = null
private var _font: String = "" private var _font: String = ""
private var _theme: Int? = null private var _theme: Int? = null
private var _enableAnalytics: Boolean? = null
init { init {
@ -85,7 +83,13 @@ class AppSettingsService {
} }
private fun refreshItemsNumber() { private fun refreshItemsNumber() {
_itemsNumber = settings.getString(API_ITEMS_NUMBER, "20").toInt() _itemsNumber = try {
settings.getString(API_ITEMS_NUMBER, "20").toInt()
} catch (e: Exception) {
settings.remove(API_ITEMS_NUMBER)
20
}
} }
fun getApiTimeout(): Long { fun getApiTimeout(): Long {
@ -95,9 +99,21 @@ class AppSettingsService {
return _apiTimeout!! return _apiTimeout!!
} }
private fun secToMs(n: Long) = n * 1000
private fun refreshApiTimeout() { private fun refreshApiTimeout() {
val settingsTimeout = settings.getLong(API_TIMEOUT, HttpTimeout.INFINITE_TIMEOUT_MS) _apiTimeout = secToMs(try {
_apiTimeout = if (settingsTimeout > 0) settingsTimeout else HttpTimeout.INFINITE_TIMEOUT_MS val settingsTimeout = settings.getString(API_TIMEOUT, "60")
if (settingsTimeout.toLong() > 0) {
settingsTimeout.toLong()
} else {
settings.remove(API_TIMEOUT)
60
}
} catch (e: Exception) {
settings.remove(API_TIMEOUT)
60
})
} }
private fun refreshBaseUrl() { private fun refreshBaseUrl() {
@ -208,19 +224,6 @@ class AppSettingsService {
return _refreshMinutes return _refreshMinutes
} }
private fun refreshHiddenTags() {
if (settings.getString(HIDDEN_TAGS, "").isNotEmpty()) {
_hiddenTags = settings.getString(HIDDEN_TAGS, "").replace("\\s".toRegex(), "").split(",")
}
}
fun getHiddenTags(): List<String> {
if (_hiddenTags != null) {
refreshHiddenTags()
}
return _hiddenTags.orEmpty()
}
private fun refreshInfiniteLoadingEnabled() { private fun refreshInfiniteLoadingEnabled() {
_infiniteLoading = settings.getBoolean(INFINITE_LOADING, false) _infiniteLoading = settings.getBoolean(INFINITE_LOADING, false)
} }
@ -305,17 +308,6 @@ class AppSettingsService {
return _staticBar == true return _staticBar == true
} }
private fun refreshAnalyticsEnabled() {
_enableAnalytics = settings.getBoolean(ENABLE_ANALYTICS, true)
}
fun isAnalyticsEnabled(): Boolean {
if (_enableAnalytics != null) {
refreshAnalyticsEnabled()
}
return _enableAnalytics == true
}
private fun refreshFont() { private fun refreshFont() {
_font = settings.getString(READER_FONT, "") _font = settings.getString(READER_FONT, "")
} }
@ -357,7 +349,6 @@ class AppSettingsService {
refreshPeriodicRefreshEnabled() refreshPeriodicRefreshEnabled()
refreshRefreshWhenChargingOnlyEnabled() refreshRefreshWhenChargingOnlyEnabled()
refreshRefreshMinutes() refreshRefreshMinutes()
refreshHiddenTags()
refreshInfiniteLoadingEnabled() refreshInfiniteLoadingEnabled()
refreshItemCachingEnabled() refreshItemCachingEnabled()
refreshNotifyNewItemsEnabled() refreshNotifyNewItemsEnabled()
@ -367,7 +358,6 @@ class AppSettingsService {
refreshFont() refreshFont()
refreshStaticBarEnabled() refreshStaticBarEnabled()
refreshCurrentTheme() refreshCurrentTheme()
refreshAnalyticsEnabled()
} }
fun refreshLoginInformation( fun refreshLoginInformation(
@ -461,7 +451,6 @@ class AppSettingsService {
const val PERIODIC_REFRESH_MINUTES = "periodic_refresh_minutes" const val PERIODIC_REFRESH_MINUTES = "periodic_refresh_minutes"
const val HIDDEN_TAGS = "hidden_tags"
const val INFINITE_LOADING = "infinite_loading" const val INFINITE_LOADING = "infinite_loading"
@ -469,6 +458,5 @@ class AppSettingsService {
const val CURRENT_THEME = "currentMode" const val CURRENT_THEME = "currentMode"
const val ENABLE_ANALYTICS = "enable_analytics"
} }
} }

View File

@ -51,7 +51,8 @@ fun ITEM.toView(): SelfossModel.Item =
this.icon, this.icon,
this.link, this.link,
this.sourcetitle, this.sourcetitle,
this.tags.split(",") this.tags.split(","),
this.author
) )
fun SelfossModel.Item.toEntity(): ITEM = fun SelfossModel.Item.toEntity(): ITEM =
@ -66,5 +67,6 @@ fun SelfossModel.Item.toEntity(): ITEM =
this.icon, this.icon,
this.link, this.link,
this.sourcetitle.getHtmlDecoded(), this.sourcetitle.getHtmlDecoded(),
this.tags.joinToString(",") this.tags.joinToString(","),
this.author
) )

View File

@ -0,0 +1,6 @@
CREATE TABLE ITEM_BACKUP AS SELECT `id`, `datetime`, `title`, `content`,
`unread`, `starred`, `thumbnail`, `icon`, `link`, `sourcetitle`,
`tags` FROM ITEM;
ALTER TABLE ITEM_BACKUP ADD COLUMN `author` TEXT;
DROP TABLE ITEM;
ALTER TABLE ITEM_BACKUP RENAME TO ITEM;

View File

@ -10,6 +10,7 @@ CREATE TABLE ITEM (
`link` TEXT NOT NULL, `link` TEXT NOT NULL,
`sourcetitle` TEXT NOT NULL, `sourcetitle` TEXT NOT NULL,
`tags` TEXT NOT NULL, `tags` TEXT NOT NULL,
`author` TEXT,
PRIMARY KEY(`id`) PRIMARY KEY(`id`)
); );
@ -26,5 +27,8 @@ INSERT OR REPLACE INTO ITEM VALUES ?;
deleteItem: deleteItem:
DELETE FROM ITEM WHERE `id` = ?; DELETE FROM ITEM WHERE `id` = ?;
deleteItemsWhereSource:
DELETE FROM ITEM WHERE `sourcetitle` = ?;
updateItem: updateItem:
UPDATE ITEM SET `datetime` = ?, `title` = ?, `content` = ?, `unread` = ?, `starred` = ?, `thumbnail` = ?, `icon` = ?, `link` = ?, `sourcetitle` = ?, `tags` = ? WHERE `id` = ?; UPDATE ITEM SET `datetime` = ?, `title` = ?, `content` = ?, `unread` = ?, `starred` = ?, `thumbnail` = ?, `icon` = ?, `link` = ?, `sourcetitle` = ?, `tags` = ?, `author` = ? WHERE `id` = ?;

5
sonar-project.properties Normal file
View File

@ -0,0 +1,5 @@
sonar.projectKey=RFS2
sonar.coverage.jacoco.xmlReportPaths=build/reports/kover/merged/xml/report.xml
sonar.sourceEncoding=UTF-8
sonar.sources=.
sonar.exclusions=shared/src/iosArm64Main/**, shared/src/iosX64Main/**, docs/**