Debug trying to fix context issues. (#174)
All checks were successful
Check master code / build (push) Successful in 7m44s
Create tag / build (push) Successful in 6m27s
Create tag / createTagAndChangelog (push) Successful in 37s
Create tag / release (push) Successful in 4m27s

Reviewed-on: #174
Co-authored-by: Amine <amine.bouabdallaoui@pm.me>
Co-committed-by: Amine <amine.bouabdallaoui@pm.me>
This commit is contained in:
2025-01-11 20:35:27 +00:00
committed by Amine Bouabdallaoui
parent 54dbda76ab
commit c79ab5e92b
74 changed files with 2077 additions and 1036 deletions

View File

@ -4,12 +4,13 @@ import android.content.Context
import app.cash.sqldelight.db.SqlDriver
import app.cash.sqldelight.driver.android.AndroidSqliteDriver
actual class DriverFactory(private val context: Context) {
actual fun createDriver(): SqlDriver {
return AndroidSqliteDriver(
actual class DriverFactory(
private val context: Context,
) {
actual fun createDriver(): SqlDriver =
AndroidSqliteDriver(
ReaderForSelfossDB.Schema,
context,
"ReaderForSelfossV2-android.db"
"ReaderForSelfossV2-android.db",
)
}
}
}

View File

@ -8,16 +8,20 @@ class NaiveTrustManager : X509TrustManager {
override fun checkClientTrusted(
chain: Array<out X509Certificate>?,
authType: String?,
) {}
) {
// Nothing
}
override fun checkServerTrusted(
chain: Array<out X509Certificate>?,
authType: String?,
) {}
) {
// Nothing
}
override fun getAcceptedIssuers(): Array<out X509Certificate> = arrayOf()
}
actual fun setupInsecureHTTPEngine(config: CIOEngineConfig) {
actual fun setupInsecureHttpEngine(config: CIOEngineConfig) {
config.https.trustManager = NaiveTrustManager()
}

View File

@ -1,9 +1,9 @@
package bou.amine.apps.readerforselfossv2.utils
import android.text.format.DateUtils
import io.github.aakira.napier.Napier
import kotlinx.datetime.*
import kotlinx.datetime.Clock
@Suppress("detekt:UtilityClassWithPublicConstructor")
actual class DateUtils {
actual companion object {
actual fun parseRelativeDate(dateString: String): String {

View File

@ -4,46 +4,36 @@ import android.net.Uri
import android.text.Html
import bou.amine.apps.readerforselfossv2.model.SelfossModel
import org.jsoup.Jsoup
import java.util.*
import java.util.Locale
actual fun String.getHtmlDecoded(): String {
return Html.fromHtml(this).toString()
}
actual fun String.getHtmlDecoded(): String = Html.fromHtml(this).toString()
actual fun SelfossModel.Item.getIcon(baseUrl: String): String {
return constructUrl(baseUrl, "favicons", icon)
}
actual fun SelfossModel.Item.getIcon(baseUrl: String): String = constructUrl(baseUrl, "favicons", icon)
actual fun SelfossModel.Item.getThumbnail(baseUrl: String): String {
return constructUrl(baseUrl, "thumbnails", thumbnail)
}
actual fun SelfossModel.Item.getThumbnail(baseUrl: String): String = constructUrl(baseUrl, "thumbnails", thumbnail)
val IMAGE_EXTENSION_REGEXP = """\.(jpg|jpeg|png|webp)""".toRegex()
actual fun SelfossModel.Item.getImages(): ArrayList<String> {
val allImages = ArrayList<String>()
for (image in Jsoup.parse(content).getElementsByTag("img")) {
val url = image.attr("src")
if (url.lowercase(Locale.US).contains(".jpg") ||
url.lowercase(Locale.US).contains(".jpeg") ||
url.lowercase(Locale.US).contains(".png") ||
url.lowercase(Locale.US).contains(".webp")
) {
if (IMAGE_EXTENSION_REGEXP.containsMatchIn(url.lowercase(Locale.US))) {
allImages.add(url)
}
}
return allImages
}
actual fun SelfossModel.Source.getIcon(baseUrl: String): String {
return constructUrl(baseUrl, "favicons", icon)
}
actual fun SelfossModel.Source.getIcon(baseUrl: String): String = constructUrl(baseUrl, "favicons", icon)
actual fun constructUrl(
baseUrl: String,
path: String,
file: String?,
): String {
return if (file == null || file == "null" || file.isEmpty()) {
): String =
if (file == null || file == "null" || file.isEmpty()) {
""
} else {
val baseUriBuilder = Uri.parse(baseUrl).buildUpon()
@ -51,4 +41,3 @@ actual fun constructUrl(
baseUriBuilder.toString()
}
}

View File

@ -4,4 +4,4 @@ import app.cash.sqldelight.db.SqlDriver
expect class DriverFactory {
fun createDriver(): SqlDriver
}
}

View File

@ -1,4 +1,4 @@
package bou.amine.apps.readerforselfossv2.DI
package bou.amine.apps.readerforselfossv2.di
import bou.amine.apps.readerforselfossv2.rest.MercuryApi
import bou.amine.apps.readerforselfossv2.rest.SelfossApi

View File

@ -1,13 +1,16 @@
@file:Suppress("detekt:LongParameterList")
package bou.amine.apps.readerforselfossv2.model
import kotlinx.serialization.Serializable
class MercuryModel {
@Suppress("detekt:ConstructorParameterNaming")
@Serializable
class ParsedContent(
val title: String? = null,
val content: String? = null,
val lead_image_url: String? = null, // NOSONAR
val lead_image_url: String? = null,
val url: String? = null,
val error: Boolean? = null,
val message: String? = null,

View File

@ -3,19 +3,20 @@ package bou.amine.apps.readerforselfossv2.model
import kotlinx.serialization.Serializable
@Serializable
class SuccessResponse(val success: Boolean) {
class SuccessResponse(
val success: Boolean,
) {
val isSuccess: Boolean
get() = success
}
class StatusAndData<T>(val success: Boolean, val data: T? = null) {
class StatusAndData<T>(
val success: Boolean,
val data: T? = null,
) {
companion object {
fun <T> succes(d: T): StatusAndData<T> {
return StatusAndData(true, d)
}
fun <T> succes(d: T): StatusAndData<T> = StatusAndData(true, d)
fun <T> error(): StatusAndData<T> {
return StatusAndData(false)
}
fun <T> error(): StatusAndData<T> = StatusAndData(false)
}
}

View File

@ -1,3 +1,5 @@
@file:Suppress("detekt:LongParameterList")
package bou.amine.apps.readerforselfossv2.model
import bou.amine.apps.readerforselfossv2.utils.DateUtils
@ -18,6 +20,10 @@ import kotlinx.serialization.json.booleanOrNull
import kotlinx.serialization.json.int
import kotlinx.serialization.json.jsonPrimitive
class ModelException(
message: String,
) : Throwable(message)
class SelfossModel {
@Serializable
data class Tag(
@ -141,7 +147,7 @@ class SelfossModel {
}
if (stringUrl.isEmptyOrNullOrNullString()) {
throw Exception("Link ${link} was translated to ${stringUrl}, but was empty. Handle this.")
throw ModelException("Link $link was translated to $stringUrl, but was empty. Handle this.")
}
return stringUrl
@ -170,14 +176,13 @@ class SelfossModel {
}
}
// TODO: this seems to be super slow.
// 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())) {
override fun deserialize(decoder: Decoder): List<String> =
when (val json = ((decoder as JsonDecoder).decodeJsonElement())) {
is JsonArray -> json.toList().map { it.toString().replace("^\"|\"$".toRegex(), "") }
else -> json.toString().split(",")
}
}
override val descriptor: SerialDescriptor
get() = PrimitiveSerialDescriptor("tags", PrimitiveKind.STRING)
@ -188,7 +193,7 @@ class SelfossModel {
) {
encoder.encodeCollection(
PrimitiveSerialDescriptor("tags", PrimitiveKind.STRING),
value.size
value.size,
) { this.toString() }
}
}
@ -204,10 +209,11 @@ class SelfossModel {
}
override val descriptor: SerialDescriptor
get() = PrimitiveSerialDescriptor(
"BooleanOrIntForSomeSelfossVersions",
PrimitiveKind.BOOLEAN
)
get() =
PrimitiveSerialDescriptor(
"BooleanOrIntForSomeSelfossVersions",
PrimitiveKind.BOOLEAN,
)
override fun serialize(
encoder: Encoder,
@ -216,4 +222,4 @@ class SelfossModel {
TODO("Not yet implemented")
}
}
}
}

View File

@ -1,12 +1,23 @@
@file:Suppress("detekt:TooManyFunctions")
package bou.amine.apps.readerforselfossv2.repository
import bou.amine.apps.readerforselfossv2.dao.*
import bou.amine.apps.readerforselfossv2.dao.ACTION
import bou.amine.apps.readerforselfossv2.dao.DriverFactory
import bou.amine.apps.readerforselfossv2.dao.ITEM
import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB
import bou.amine.apps.readerforselfossv2.dao.SOURCE
import bou.amine.apps.readerforselfossv2.dao.TAG
import bou.amine.apps.readerforselfossv2.model.NetworkUnavailableException
import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.model.StatusAndData
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import bou.amine.apps.readerforselfossv2.utils.*
import bou.amine.apps.readerforselfossv2.utils.ItemType
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
import bou.amine.apps.readerforselfossv2.utils.toEntity
import bou.amine.apps.readerforselfossv2.utils.toParsedDate
import bou.amine.apps.readerforselfossv2.utils.toView
import io.github.aakira.napier.Napier
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -14,6 +25,8 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
private const val MAX_ITEMS_NUMBER = 200
class Repository(
private val api: SelfossApi,
private val appSettingsService: AppSettingsService,
@ -118,7 +131,7 @@ class Repository(
null,
null,
null,
200,
MAX_ITEMS_NUMBER,
)
return if (items.success && items.data != null) {
items.data
@ -130,6 +143,7 @@ class Repository(
}
}
@Suppress("detekt:ForbiddenComment")
suspend fun reloadBadges(): Boolean {
var success = false
if (isNetworkAvailable()) {
@ -170,8 +184,8 @@ class Repository(
}
}
suspend fun getSpouts(): Map<String, SelfossModel.Spout> {
return if (isNetworkAvailable()) {
suspend fun getSpouts(): Map<String, SelfossModel.Spout> =
if (isNetworkAvailable()) {
val spouts = api.spouts()
if (spouts.success && spouts.data != null) {
spouts.data
@ -181,7 +195,6 @@ class Repository(
} else {
throw NetworkUnavailableException()
}
}
suspend fun getSourcesDetailsOrStats(): ArrayList<SelfossModel.Source> {
var sources = ArrayList<SelfossModel.Source>()
@ -234,14 +247,13 @@ class Repository(
return success
}
private suspend fun markAsReadById(id: Int): Boolean {
return if (isNetworkAvailable()) {
private suspend fun markAsReadById(id: Int): Boolean =
if (isNetworkAvailable()) {
api.markAsRead(id.toString()).isSuccess
} else {
insertDBAction(id.toString(), read = true)
true
}
}
suspend fun unmarkAsRead(item: SelfossModel.Item): Boolean {
val success = unmarkAsReadById(item.id)
@ -252,14 +264,13 @@ class Repository(
return success
}
private suspend fun unmarkAsReadById(id: Int): Boolean {
return if (isNetworkAvailable()) {
private suspend fun unmarkAsReadById(id: Int): Boolean =
if (isNetworkAvailable()) {
api.unmarkAsRead(id.toString()).isSuccess
} else {
insertDBAction(id.toString(), unread = true)
true
}
}
suspend fun starr(item: SelfossModel.Item): Boolean {
val success = starrById(item.id)
@ -270,14 +281,13 @@ class Repository(
return success
}
private suspend fun starrById(id: Int): Boolean {
return if (isNetworkAvailable()) {
private suspend fun starrById(id: Int): Boolean =
if (isNetworkAvailable()) {
api.starr(id.toString()).isSuccess
} else {
insertDBAction(id.toString(), starred = true)
true
}
}
suspend fun unstarr(item: SelfossModel.Item): Boolean {
val success = unstarrById(item.id)
@ -288,14 +298,13 @@ class Repository(
return success
}
private suspend fun unstarrById(id: Int): Boolean {
return if (isNetworkAvailable()) {
private suspend fun unstarrById(id: Int): Boolean =
if (isNetworkAvailable()) {
api.unstarr(id.toString()).isSuccess
} else {
insertDBAction(id.toString(), starred = true)
true
}
}
suspend fun markAllAsRead(items: ArrayList<SelfossModel.Item>): Boolean {
var success = false
@ -361,12 +370,13 @@ class Repository(
): Boolean {
var response = false
if (isNetworkAvailable()) {
response = api.createSourceForVersion(
title,
url,
spout,
tags,
).isSuccess == true
response = api
.createSourceForVersion(
title,
url,
spout,
tags,
).isSuccess == true
}
return response
@ -407,13 +417,12 @@ class Repository(
return success
}
suspend fun updateRemote(): Boolean {
return if (isNetworkAvailable()) {
suspend fun updateRemote(): Boolean =
if (isNetworkAvailable()) {
api.update().data.equals("finished")
} else {
false
}
}
suspend fun login(): Boolean {
var result = false
@ -422,7 +431,7 @@ class Repository(
val response = api.login()
result = response.isSuccess == true
} catch (cause: Throwable) {
Napier.e("login failed", cause, tag = "RepositoryImpl.login")
Napier.e("login failed", cause, tag = "Repository.login")
}
}
return result
@ -436,7 +445,7 @@ class Repository(
// a random rss feed, that would throw a NoTransformationFoundException
fetchFailed = !api.getItemsWithoutCatch().success
} catch (e: Throwable) {
Napier.e("checkIfFetchFails failed", e, tag = "RepositoryImpl.shouldBeSelfossInstance")
Napier.e("checkIfFetchFails failed", e, tag = "Repository.shouldBeSelfossInstance")
}
}
@ -448,10 +457,10 @@ class Repository(
try {
val response = api.logout()
if (!response.isSuccess) {
Napier.e("Couldn't logout.", tag = "RepositoryImpl.logout")
Napier.e("Couldn't logout.", tag = "Repository.logout")
}
} catch (cause: Throwable) {
Napier.e("logout failed", cause, tag = "RepositoryImpl.logout")
Napier.e("logout failed", cause, tag = "Repository.logout")
}
appSettingsService.clearAll()
} else {
@ -555,6 +564,7 @@ class Repository(
item.id.toString(),
)
@Suppress("detekt:SwallowedException")
suspend fun tryToCacheItemsAndGetNewOnes(): List<SelfossModel.Item> {
try {
val newItems = getMaxItemsForBackground(ItemType.UNREAD)
@ -578,16 +588,19 @@ class Repository(
markAsReadById(action.articleid.toInt()),
action,
)
action.unread ->
doAndReportOnFail(
unmarkAsReadById(action.articleid.toInt()),
action,
)
action.starred ->
doAndReportOnFail(
starrById(action.articleid.toInt()),
action,
)
action.unstarred ->
doAndReportOnFail(
unstarrById(action.articleid.toInt()),
@ -618,9 +631,7 @@ class Repository(
_readerItems = readerItems
}
fun getReaderItems(): ArrayList<SelfossModel.Item> {
return _readerItems
}
fun getReaderItems(): ArrayList<SelfossModel.Item> = _readerItems
fun migrate(driverFactory: DriverFactory) {
ReaderForSelfossDB.Schema.migrate(driverFactory.createDriver(), 0, 1)
@ -634,7 +645,5 @@ class Repository(
_selectedSource = null
}
fun getSelectedSource(): SelfossModel.SourceDetail? {
return _selectedSource
}
fun getSelectedSource(): SelfossModel.SourceDetail? = _selectedSource
}

View File

@ -17,8 +17,8 @@ import kotlinx.serialization.json.Json
class MercuryApi {
var client = createHttpClient()
private fun createHttpClient(): HttpClient {
return HttpClient {
private fun createHttpClient(): HttpClient =
HttpClient {
install(HttpCache)
install(ContentNegotiation) {
json(
@ -40,7 +40,6 @@ class MercuryApi {
}
expectSuccess = false
}
}
suspend fun query(url: String): StatusAndData<MercuryModel.ParsedContent> =
bodyOrFailure(
@ -48,4 +47,4 @@ class MercuryApi {
parameter("link", url)
},
)
}
}

View File

@ -3,23 +3,28 @@ 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.*
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.client.request.delete
import io.ktor.client.request.forms.submitForm
import io.ktor.client.request.get
import io.ktor.client.request.post
import io.ktor.client.request.url
import io.ktor.client.statement.HttpResponse
import io.ktor.http.HttpStatusCode
import io.ktor.http.Parameters
import io.ktor.http.isSuccess
suspend fun responseOrSuccessIf404(r: HttpResponse?): SuccessResponse {
return if (r != null && r.status === HttpStatusCode.NotFound) {
suspend fun responseOrSuccessIf404(r: HttpResponse?): SuccessResponse =
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()) {
suspend fun maybeResponse(r: HttpResponse?): SuccessResponse =
if (r != null && r.status.isSuccess()) {
r.body()
} else {
if (r != null) {
@ -27,8 +32,8 @@ suspend fun maybeResponse(r: HttpResponse?): SuccessResponse {
}
SuccessResponse(false)
}
}
@Suppress("detekt:SwallowedException")
suspend inline fun <reified T> bodyOrFailure(r: HttpResponse?): StatusAndData<T> {
try {
return if (r != null && r.status.isSuccess()) {
@ -98,4 +103,4 @@ suspend fun HttpClient.tryToSubmitForm(
url(url)
block()
}
}
}

View File

@ -1,3 +1,5 @@
@file:Suppress("detekt:TooManyFunctions", "detekt:LongParameterList", "detekt:LargeClass")
package bou.amine.apps.readerforselfossv2.rest
import bou.amine.apps.readerforselfossv2.model.SelfossModel
@ -33,16 +35,20 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json
expect fun setupInsecureHTTPEngine(config: CIOEngineConfig)
expect fun setupInsecureHttpEngine(config: CIOEngineConfig)
class SelfossApi(private val appSettingsService: AppSettingsService) {
private const val VERSION_WHERE_POST_LOGIN_SHOULD_WORK = 5
class SelfossApi(
private val appSettingsService: AppSettingsService,
) {
var client = createHttpClient()
fun createHttpClient() =
HttpClient(CIO) {
if (appSettingsService.getSelfSigned()) {
engine {
setupInsecureHTTPEngine(this)
setupInsecureHttpEngine(this)
}
}
install(HttpCache)
@ -105,12 +111,14 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
private fun hasLoginInfo() =
appSettingsService.getUserName().isNotEmpty() &&
appSettingsService.getPassword()
.isNotEmpty()
appSettingsService
.getPassword()
.isNotEmpty()
suspend fun login(): SuccessResponse =
if (appSettingsService.getUserName().isNotEmpty() &&
appSettingsService.getPassword()
appSettingsService
.getPassword()
.isNotEmpty()
) {
if (shouldHavePostLogin()) {
@ -127,8 +135,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
client.tryToGet(url("/login")) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
if (appSettingsService.getBasicUserName()
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
if (appSettingsService
.getBasicUserName()
.isNotEmpty() &&
appSettingsService.getBasicPassword().isNotEmpty()
) {
headers {
append(
@ -150,8 +160,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
client.tryToPost(url("/login")) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
if (appSettingsService.getBasicUserName()
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
if (appSettingsService
.getBasicUserName()
.isNotEmpty() &&
appSettingsService.getBasicPassword().isNotEmpty()
) {
headers {
append(
@ -168,8 +180,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
},
)
private fun shouldHaveNewLogout() =
appSettingsService.getApiVersion() >= 5 // We are missing 4.1.0
private fun shouldHaveNewLogout() = appSettingsService.getApiVersion() >= VERSION_WHERE_POST_LOGIN_SHOULD_WORK // We are missing 4.1.0
suspend fun logout(): SuccessResponse =
if (shouldHaveNewLogout()) {
@ -181,8 +192,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
private suspend fun maybeLogoutIfAvailable() =
responseOrSuccessIf404(
client.tryToGet(url("/logout")) {
if (appSettingsService.getBasicUserName()
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
if (appSettingsService
.getBasicUserName()
.isNotEmpty() &&
appSettingsService.getBasicPassword().isNotEmpty()
) {
headers {
append(
@ -202,8 +215,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
private suspend fun doLogout() =
maybeResponse(
client.tryToDelete(url("/api/session/current")) {
if (appSettingsService.getBasicUserName()
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
if (appSettingsService
.getBasicUserName()
.isNotEmpty() &&
appSettingsService.getBasicPassword().isNotEmpty()
) {
headers {
append(
@ -242,8 +257,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
parameter("updatedsince", updatedSince)
parameter("items", items ?: appSettingsService.getItemsNumber())
parameter("offset", offset)
if (appSettingsService.getBasicUserName()
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
if (appSettingsService
.getBasicUserName()
.isNotEmpty() &&
appSettingsService.getBasicPassword().isNotEmpty()
) {
headers {
append(
@ -269,8 +286,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
}
parameter("type", "all")
parameter("items", 1)
if (appSettingsService.getBasicUserName()
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
if (appSettingsService
.getBasicUserName()
.isNotEmpty() &&
appSettingsService.getBasicPassword().isNotEmpty()
) {
headers {
append(
@ -294,8 +313,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}
if (appSettingsService.getBasicUserName()
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
if (appSettingsService
.getBasicUserName()
.isNotEmpty() &&
appSettingsService.getBasicPassword().isNotEmpty()
) {
headers {
append(
@ -319,8 +340,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}
if (appSettingsService.getBasicUserName()
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
if (appSettingsService
.getBasicUserName()
.isNotEmpty() &&
appSettingsService.getBasicPassword().isNotEmpty()
) {
headers {
append(
@ -344,8 +367,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}
if (appSettingsService.getBasicUserName()
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
if (appSettingsService
.getBasicUserName()
.isNotEmpty() &&
appSettingsService.getBasicPassword().isNotEmpty()
) {
headers {
append(
@ -369,8 +394,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}
if (appSettingsService.getBasicUserName()
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
if (appSettingsService
.getBasicUserName()
.isNotEmpty() &&
appSettingsService.getBasicPassword().isNotEmpty()
) {
headers {
append(
@ -394,8 +421,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}
if (appSettingsService.getBasicUserName()
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
if (appSettingsService
.getBasicUserName()
.isNotEmpty() &&
appSettingsService.getBasicPassword().isNotEmpty()
) {
headers {
append(
@ -419,8 +448,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}
if (appSettingsService.getBasicUserName()
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
if (appSettingsService
.getBasicUserName()
.isNotEmpty() &&
appSettingsService.getBasicPassword().isNotEmpty()
) {
headers {
append(
@ -440,8 +471,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
suspend fun apiInformation(): StatusAndData<SelfossModel.ApiInformation> =
bodyOrFailure(
client.tryToGet(url("/api/about")) {
if (appSettingsService.getBasicUserName()
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
if (appSettingsService
.getBasicUserName()
.isNotEmpty() &&
appSettingsService.getBasicPassword().isNotEmpty()
) {
headers {
append(
@ -465,8 +498,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}
if (appSettingsService.getBasicUserName()
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
if (appSettingsService
.getBasicUserName()
.isNotEmpty() &&
appSettingsService.getBasicPassword().isNotEmpty()
) {
headers {
append(
@ -490,8 +525,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}
if (appSettingsService.getBasicUserName()
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
if (appSettingsService
.getBasicUserName()
.isNotEmpty() &&
appSettingsService.getBasicPassword().isNotEmpty()
) {
headers {
append(
@ -515,8 +552,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}
if (appSettingsService.getBasicUserName()
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
if (appSettingsService
.getBasicUserName()
.isNotEmpty() &&
appSettingsService.getBasicPassword().isNotEmpty()
) {
headers {
append(
@ -540,8 +579,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}
if (appSettingsService.getBasicUserName()
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
if (appSettingsService
.getBasicUserName()
.isNotEmpty() &&
appSettingsService.getBasicPassword().isNotEmpty()
) {
headers {
append(
@ -563,16 +604,18 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
client.tryToSubmitForm(
url = url("/mark"),
formParameters =
Parameters.build {
if (!shouldHavePostLogin()) {
append("username", appSettingsService.getUserName())
append("password", appSettingsService.getPassword())
}
ids.map { append("ids[]", it) }
},
Parameters.build {
if (!shouldHavePostLogin()) {
append("username", appSettingsService.getUserName())
append("password", appSettingsService.getPassword())
}
ids.map { append("ids[]", it) }
},
block = {
if (appSettingsService.getBasicUserName()
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
if (appSettingsService
.getBasicUserName()
.isNotEmpty() &&
appSettingsService.getBasicPassword().isNotEmpty()
) {
headers {
append(
@ -614,19 +657,21 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
client.tryToSubmitForm(
url = url("/source"),
formParameters =
Parameters.build {
if (!shouldHavePostLogin()) {
append("username", appSettingsService.getUserName())
append("password", appSettingsService.getPassword())
}
append("title", title)
append("url", url)
append("spout", spout)
append(tagsParamName, tags)
},
Parameters.build {
if (!shouldHavePostLogin()) {
append("username", appSettingsService.getUserName())
append("password", appSettingsService.getPassword())
}
append("title", title)
append("url", url)
append("spout", spout)
append(tagsParamName, tags)
},
block = {
if (appSettingsService.getBasicUserName()
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
if (appSettingsService
.getBasicUserName()
.isNotEmpty() &&
appSettingsService.getBasicPassword().isNotEmpty()
) {
headers {
append(
@ -669,19 +714,21 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
client.tryToSubmitForm(
url = url("/source/$id"),
formParameters =
Parameters.build {
if (!shouldHavePostLogin()) {
append("username", appSettingsService.getUserName())
append("password", appSettingsService.getPassword())
}
append("title", title)
append("url", url)
append("spout", spout)
append(tagsParamName, tags)
},
Parameters.build {
if (!shouldHavePostLogin()) {
append("username", appSettingsService.getUserName())
append("password", appSettingsService.getPassword())
}
append("title", title)
append("url", url)
append("spout", spout)
append(tagsParamName, tags)
},
block = {
if (appSettingsService.getBasicUserName()
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
if (appSettingsService
.getBasicUserName()
.isNotEmpty() &&
appSettingsService.getBasicPassword().isNotEmpty()
) {
headers {
append(
@ -705,8 +752,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}
if (appSettingsService.getBasicUserName()
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
if (appSettingsService
.getBasicUserName()
.isNotEmpty() &&
appSettingsService.getBasicPassword().isNotEmpty()
) {
headers {
append(
@ -722,4 +771,4 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
}
},
)
}
}

View File

@ -1,3 +1,5 @@
@file:Suppress("detekt:TooManyFunctions")
package bou.amine.apps.readerforselfossv2.service
import com.russhwolf.settings.Settings
@ -121,4 +123,4 @@ class ACRASettings : Settings {
longs.remove(key)
strings.remove(key)
}
}
}

View File

@ -1,8 +1,22 @@
@file:Suppress("detekt:TooManyFunctions")
package bou.amine.apps.readerforselfossv2.service
import com.russhwolf.settings.Settings
class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
private const val DEFAULT_FONT_SIZE = 16
private const val DEFAULT_REFRESH_MINUTES = 360L
private const val MIN_REFRESH_MINUTES = 15L
private const val DEFAULT_API_TIMEOUT = 60L
private const val DEFAULT_ITEMS_NUMBER = 20
class AppSettingsService(
acraSenderServiceProcess: Boolean = false,
) {
val settings: Settings =
if (acraSenderServiceProcess) {
ACRASettings()
@ -11,37 +25,37 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
}
// Api related
private var _apiVersion: Int = -1
private var _publicAccess: Boolean? = null
private var _selfSigned: Boolean? = null
private var _baseUrl: String = ""
private var _userName: String = ""
private var _basicUserName: String = ""
private var _password: String = ""
private var _basicPassword: String = ""
private var apiVersion: Int = -1
private var publicAccess: Boolean? = null
private var selfSigned: Boolean? = null
private var baseUrl: String = ""
private var userName: String = ""
private var basicUserName: String = ""
private var password: String = ""
private var basicPassword: String = ""
// User settings related
private var _itemsCaching: Boolean? = null
private var _articleViewer: Boolean? = null
private var _shouldBeCardView: Boolean? = null
private var _displayUnreadCount: Boolean? = null
private var _displayAllCount: Boolean? = null
private var _fullHeightCards: Boolean? = null
private var _updateSources: Boolean? = null
private var _periodicRefresh: Boolean? = null
private var _refreshWhenChargingOnly: Boolean? = null
private var _infiniteLoading: Boolean? = null
private var _notifyNewItems: Boolean? = null
private var _itemsNumber: Int? = null
private var _apiTimeout: Long? = null
private var _refreshMinutes: Long = 360
private var _markOnScroll: Boolean? = null
private var _activeAlignment: Int? = null
private var itemsCaching: Boolean? = null
private var articleViewer: Boolean? = null
private var shouldBeCardView: Boolean? = null
private var displayUnreadCount: Boolean? = null
private var displayAllCount: Boolean? = null
private var fullHeightCards: Boolean? = null
private var updateSources: Boolean? = null
private var periodicRefresh: Boolean? = null
private var refreshWhenChargingOnly: Boolean? = null
private var infiniteLoading: Boolean? = null
private var notifyNewItems: Boolean? = null
private var itemsNumber: Int? = null
private var apiTimeout: Long? = null
private var refreshMinutes: Long = DEFAULT_REFRESH_MINUTES
private var markOnScroll: Boolean? = null
private var activeAlignment: Int? = null
private var _fontSize: Int? = null
private var _staticBar: Boolean? = null
private var _font: String = ""
private var _theme: Int? = null
private var fontSize: Int? = null
private var staticBar: Boolean? = null
private var font: String = ""
private var theme: Int? = null
init {
refreshApiSettings()
@ -49,11 +63,11 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
}
fun getApiVersion(): Int {
if (_apiVersion == -1) {
if (apiVersion == -1) {
refreshApiVersion()
return _apiVersion
return apiVersion
}
return _apiVersion
return apiVersion
}
fun updateApiVersion(apiMajorVersion: Int) {
@ -62,14 +76,14 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
}
private fun refreshApiVersion() {
_apiVersion = settings.getInt(API_VERSION_MAJOR, -1)
apiVersion = settings.getInt(API_VERSION_MAJOR, -1)
}
fun getPublicAccess(): Boolean {
if (_publicAccess == null) {
if (publicAccess == null) {
refreshPublicAccess()
}
return _publicAccess!!
return publicAccess!!
}
fun updatePublicAccess(publicAccess: Boolean) {
@ -78,14 +92,14 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
}
private fun refreshPublicAccess() {
_publicAccess = settings.getBoolean(API_PUBLIC_ACCESS, false)
publicAccess = settings.getBoolean(API_PUBLIC_ACCESS, false)
}
fun getSelfSigned(): Boolean {
if (_selfSigned == null) {
if (selfSigned == null) {
refreshSelfSigned()
}
return _selfSigned!!
return selfSigned!!
}
fun updateSelfSigned(selfSigned: Boolean) {
@ -94,312 +108,315 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
}
private fun refreshSelfSigned() {
_selfSigned = settings.getBoolean(API_SELF_SIGNED, false)
selfSigned = settings.getBoolean(API_SELF_SIGNED, false)
}
fun getBaseUrl(): String {
if (_baseUrl.isEmpty()) {
if (baseUrl.isEmpty()) {
refreshBaseUrl()
}
return _baseUrl
return baseUrl
}
fun getUserName(): String {
if (_userName.isEmpty()) {
if (userName.isEmpty()) {
refreshUsername()
}
return _userName
return userName
}
fun getPassword(): String {
if (_password.isEmpty()) {
if (password.isEmpty()) {
refreshPassword()
}
return _password
return password
}
fun getBasicUserName(): String {
if (_basicUserName.isEmpty()) {
if (basicUserName.isEmpty()) {
refreshBasicUsername()
}
return _basicUserName
return basicUserName
}
fun getBasicPassword(): String {
if (_basicPassword.isEmpty()) {
if (basicPassword.isEmpty()) {
refreshBasicPassword()
}
return _basicPassword
return basicPassword
}
fun getItemsNumber(): Int {
if (_itemsNumber == null) {
if (itemsNumber == null) {
refreshItemsNumber()
}
return _itemsNumber!!
return itemsNumber!!
}
@Suppress("detekt:SwallowedException")
private fun refreshItemsNumber() {
_itemsNumber =
itemsNumber =
try {
settings.getString(API_ITEMS_NUMBER, "20").toInt()
settings.getString(API_ITEMS_NUMBER, DEFAULT_ITEMS_NUMBER.toString()).toInt()
} catch (e: Exception) {
settings.remove(API_ITEMS_NUMBER)
20
DEFAULT_ITEMS_NUMBER
}
}
fun getApiTimeout(): Long {
if (_apiTimeout == null) {
if (apiTimeout == null) {
refreshApiTimeout()
}
return _apiTimeout!!
return apiTimeout!!
}
@Suppress("detekt:MagicNumber")
private fun secToMs(n: Long) = n * 1000
@Suppress("detekt:SwallowedException")
private fun refreshApiTimeout() {
_apiTimeout =
apiTimeout =
secToMs(
try {
val settingsTimeout = settings.getString(API_TIMEOUT, "60")
val settingsTimeout = settings.getString(API_TIMEOUT, DEFAULT_API_TIMEOUT.toString())
if (settingsTimeout.toLong() > 0) {
settingsTimeout.toLong()
} else {
settings.remove(API_TIMEOUT)
60
DEFAULT_API_TIMEOUT
}
} catch (e: Exception) {
settings.remove(API_TIMEOUT)
60
DEFAULT_API_TIMEOUT
},
)
}
private fun refreshBaseUrl() {
_baseUrl = settings.getString(BASE_URL, "")
baseUrl = settings.getString(BASE_URL, "")
}
private fun refreshUsername() {
_userName = settings.getString(LOGIN, "")
userName = settings.getString(LOGIN, "")
}
private fun refreshPassword() {
_password = settings.getString(PASSWORD, "")
password = settings.getString(PASSWORD, "")
}
private fun refreshBasicUsername() {
_basicUserName = settings.getString(BASIC_LOGIN, "")
basicUserName = settings.getString(BASIC_LOGIN, "")
}
private fun refreshBasicPassword() {
_basicPassword = settings.getString(BASIC_PASSWORD, "")
basicPassword = settings.getString(BASIC_PASSWORD, "")
}
private fun refreshArticleViewerEnabled() {
_articleViewer = settings.getBoolean(PREFER_ARTICLE_VIEWER, true)
articleViewer = settings.getBoolean(PREFER_ARTICLE_VIEWER, true)
}
fun isArticleViewerEnabled(): Boolean {
if (_articleViewer != null) {
if (articleViewer != null) {
refreshArticleViewerEnabled()
}
return _articleViewer == true
return articleViewer == true
}
private fun refreshShouldBeCardViewEnabled() {
_shouldBeCardView = settings.getBoolean(CARD_VIEW_ACTIVE, false)
shouldBeCardView = settings.getBoolean(CARD_VIEW_ACTIVE, false)
}
fun isCardViewEnabled(): Boolean {
if (_shouldBeCardView != null) {
if (shouldBeCardView != null) {
refreshShouldBeCardViewEnabled()
}
return _shouldBeCardView == true
return shouldBeCardView == true
}
private fun refreshDisplayUnreadCountEnabled() {
_displayUnreadCount = settings.getBoolean(DISPLAY_UNREAD_COUNT, true)
displayUnreadCount = settings.getBoolean(DISPLAY_UNREAD_COUNT, true)
}
fun isDisplayUnreadCountEnabled(): Boolean {
if (_displayUnreadCount != null) {
if (displayUnreadCount != null) {
refreshDisplayUnreadCountEnabled()
}
return _displayUnreadCount == true
return displayUnreadCount == true
}
private fun refreshDisplayAllCountEnabled() {
_displayAllCount = settings.getBoolean(DISPLAY_OTHER_COUNT, false)
displayAllCount = settings.getBoolean(DISPLAY_OTHER_COUNT, false)
}
fun isDisplayAllCountEnabled(): Boolean {
if (_displayAllCount != null) {
if (displayAllCount != null) {
refreshDisplayAllCountEnabled()
}
return _displayAllCount == true
return displayAllCount == true
}
private fun refreshFullHeightCardsEnabled() {
_fullHeightCards = settings.getBoolean(FULL_HEIGHT_CARDS, false)
fullHeightCards = settings.getBoolean(FULL_HEIGHT_CARDS, false)
}
fun isFullHeightCardsEnabled(): Boolean {
if (_fullHeightCards != null) {
if (fullHeightCards != null) {
refreshFullHeightCardsEnabled()
}
return _fullHeightCards == true
return fullHeightCards == true
}
private fun refreshUpdateSourcesEnabled() {
_updateSources = settings.getBoolean(UPDATE_SOURCES, true)
updateSources = settings.getBoolean(UPDATE_SOURCES, true)
}
fun isUpdateSourcesEnabled(): Boolean {
if (_updateSources != null) {
if (updateSources != null) {
refreshUpdateSourcesEnabled()
}
return _updateSources == true
return updateSources == true
}
private fun refreshPeriodicRefreshEnabled() {
_periodicRefresh = settings.getBoolean(PERIODIC_REFRESH, false)
periodicRefresh = settings.getBoolean(PERIODIC_REFRESH, false)
}
fun isPeriodicRefreshEnabled(): Boolean {
if (_periodicRefresh != null) {
if (periodicRefresh != null) {
refreshPeriodicRefreshEnabled()
}
return _periodicRefresh == true
return periodicRefresh == true
}
private fun refreshRefreshWhenChargingOnlyEnabled() {
_refreshWhenChargingOnly = settings.getBoolean(REFRESH_WHEN_CHARGING, false)
refreshWhenChargingOnly = settings.getBoolean(REFRESH_WHEN_CHARGING, false)
}
fun isRefreshWhenChargingOnlyEnabled(): Boolean {
if (_refreshWhenChargingOnly != null) {
if (refreshWhenChargingOnly != null) {
refreshRefreshWhenChargingOnlyEnabled()
}
return _refreshWhenChargingOnly == true
return refreshWhenChargingOnly == true
}
private fun refreshRefreshMinutes() {
_refreshMinutes = settings.getString(PERIODIC_REFRESH_MINUTES, "360").toLong()
if (_refreshMinutes <= 15) {
_refreshMinutes = 15
refreshMinutes = settings.getString(PERIODIC_REFRESH_MINUTES, DEFAULT_REFRESH_MINUTES.toString()).toLong()
if (refreshMinutes <= MIN_REFRESH_MINUTES) {
refreshMinutes = MIN_REFRESH_MINUTES
}
}
fun getRefreshMinutes(): Long {
if (_refreshMinutes != 360L) {
if (refreshMinutes != DEFAULT_REFRESH_MINUTES) {
refreshRefreshMinutes()
}
return _refreshMinutes
return refreshMinutes
}
private fun refreshInfiniteLoadingEnabled() {
_infiniteLoading = settings.getBoolean(INFINITE_LOADING, false)
infiniteLoading = settings.getBoolean(INFINITE_LOADING, false)
}
fun isInfiniteLoadingEnabled(): Boolean {
if (_infiniteLoading != null) {
if (infiniteLoading != null) {
refreshInfiniteLoadingEnabled()
}
return _infiniteLoading == true
return infiniteLoading == true
}
private fun refreshItemCachingEnabled() {
_itemsCaching = settings.getBoolean(ITEMS_CACHING, false)
itemsCaching = settings.getBoolean(ITEMS_CACHING, false)
}
fun isItemCachingEnabled(): Boolean {
if (_itemsCaching != null) {
if (itemsCaching != null) {
refreshItemCachingEnabled()
}
return _itemsCaching == true
return itemsCaching == true
}
private fun refreshNotifyNewItemsEnabled() {
_notifyNewItems = settings.getBoolean(NOTIFY_NEW_ITEMS, false)
notifyNewItems = settings.getBoolean(NOTIFY_NEW_ITEMS, false)
}
fun isNotifyNewItemsEnabled(): Boolean {
if (_notifyNewItems != null) {
if (notifyNewItems != null) {
refreshNotifyNewItemsEnabled()
}
return _notifyNewItems == true
return notifyNewItems == true
}
private fun refreshMarkOnScrollEnabled() {
_markOnScroll = settings.getBoolean(MARK_ON_SCROLL, false)
markOnScroll = settings.getBoolean(MARK_ON_SCROLL, false)
}
fun isMarkOnScrollEnabled(): Boolean {
if (_markOnScroll != null) {
if (markOnScroll != null) {
refreshMarkOnScrollEnabled()
}
return _markOnScroll == true
return markOnScroll == true
}
private fun refreshActiveAllignment() {
_activeAlignment = settings.getInt(TEXT_ALIGN, JUSTIFY)
activeAlignment = settings.getInt(TEXT_ALIGN, JUSTIFY)
}
fun getActiveAllignment(): Int {
if (_activeAlignment != null) {
if (activeAlignment != null) {
refreshActiveAllignment()
}
return _activeAlignment ?: JUSTIFY
return activeAlignment ?: JUSTIFY
}
fun changeAllignment(allignment: Int) {
settings.putInt(TEXT_ALIGN, allignment)
_activeAlignment = allignment
activeAlignment = allignment
}
private fun refreshFontSize() {
_fontSize = settings.getString(READER_FONT_SIZE, "16").toInt()
fontSize = settings.getString(READER_FONT_SIZE, "16").toInt()
}
fun getFontSize(): Int {
if (_fontSize != null) {
if (fontSize != null) {
refreshFontSize()
}
return _fontSize ?: 16
return fontSize ?: DEFAULT_FONT_SIZE
}
private fun refreshStaticBarEnabled() {
_staticBar = settings.getBoolean(READER_STATIC_BAR, false)
staticBar = settings.getBoolean(READER_STATIC_BAR, false)
}
fun isStaticBarEnabled(): Boolean {
if (_staticBar != null) {
if (staticBar != null) {
refreshStaticBarEnabled()
}
return _staticBar == true
return staticBar == true
}
private fun refreshFont() {
_font = settings.getString(READER_FONT, "")
font = settings.getString(READER_FONT, "")
}
fun getFont(): String {
if (_font.isEmpty()) {
if (font.isEmpty()) {
refreshFont()
}
return _font
return font
}
private fun refreshCurrentTheme() {
_theme = settings.getString(CURRENT_THEME, "-1").toInt()
theme = settings.getString(CURRENT_THEME, "-1").toInt()
}
fun getCurrentTheme(): Int {
if (_theme == null) {
if (theme == null) {
refreshCurrentTheme()
}
return _theme ?: -1
return theme ?: -1
}
fun refreshApiSettings() {
@ -478,15 +495,15 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
}
companion object {
const val translationUrl = "https://crwd.in/readerforselfoss"
const val TRANSLATION_URL = "https://crwd.in/readerforselfoss"
const val sourceUrl = "https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderForSelfoss-multiplatform"
const val SOURCE_URL = "https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderForSelfoss-multiplatform"
const val trackerUrl = "https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderForSelfoss-multiplatform/issues"
const val BUG_URL = "https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderForSelfoss-multiplatform/issues"
const val syncChannelId = "sync-channel-id"
const val SYNC_CHANNEL_ID = "sync-channel-id"
const val newItemsChannelId = "new-items-channel-id"
const val NEW_ITEMS_CHANNEL = "new-items-channel-id"
const val JUSTIFY = 1

View File

@ -4,6 +4,12 @@ import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toInstant
class DateParseException(
message: String,
e: Throwable? = null,
) : Throwable(message, e)
@Suppress("detekt:ThrowsCount")
fun String.toParsedDate(): Long {
// Possible formats are
// yyyy-mm-dd hh:mm:ss format
@ -17,17 +23,22 @@ fun String.toParsedDate(): Long {
if (this.matches(oldVersionFormat)) {
this.replace(" ", "T")
} else if (this.matches(newVersionFormat)) {
newVersionFormat.find(this)?.groups?.get(1)?.value ?: throw Exception("Couldn't parse $this")
newVersionFormat
.find(this)
?.groups
?.get(1)
?.value ?: throw DateParseException("Couldn't parse $this")
} else {
throw Exception("Unrecognized format for $this")
throw DateParseException("Unrecognized format for $this")
}
} catch (e: Exception) {
throw Exception("parseDate failed for $this", e)
throw DateParseException("parseDate failed for $this", e)
}
return LocalDateTime.parse(isoDateString).toInstant(TimeZone.currentSystemDefault()).toEpochMilliseconds()
}
@Suppress("detekt:UtilityClassWithPublicConstructor")
expect class DateUtils() {
companion object {
fun parseRelativeDate(dateString: String): String

View File

@ -17,7 +17,7 @@ fun SOURCE.toView(): SelfossModel.SourceDetail =
this.id.toInt(),
this.title,
null,
this.tags?.split(","),
this.tags.split(","),
this.spout,
this.error,
this.icon,
@ -74,6 +74,7 @@ fun SelfossModel.Item.toEntity(): ITEM =
this.author,
)
@Suppress("detekt:MagicNumber")
fun SelfossModel.Tag.getColorHexCode(): String =
if (this.color.length == 4) { // #000
val char1 = this.color.get(1)
@ -82,4 +83,4 @@ fun SelfossModel.Tag.getColorHexCode(): String =
"#$char1$char1$char2$char2$char3$char3"
} else {
this.color
}
}

View File

@ -1,6 +1,10 @@
package bou.amine.apps.readerforselfossv2.utils
enum class ItemType(val position: Int, val type: String) {
@Suppress("detekt:MagicNumber")
enum class ItemType(
val position: Int,
val type: String,
) {
UNREAD(1, "unread"),
ALL(2, "all"),
STARRED(3, "starred"),

View File

@ -2,6 +2,7 @@ package bou.amine.apps.readerforselfossv2.utils
fun String?.isEmptyOrNullOrNullString(): Boolean = this == null || this == "null" || this.isEmpty()
@Suppress("detekt:MagicNumber")
fun String.longHash(): Long {
var h = 98764321261L
val l = this.length

View File

@ -8,27 +8,29 @@ import kotlin.test.Test
import kotlin.test.assertEquals
class DatesTest {
private val newVersionDateVariant = "2022-12-24T17:00:08+00"
private val newVersionDate = "2013-04-07T13:43:00+01:00"
private val newVersionDate2 = "2013-04-07T13:43:00-01:00"
private val oldVersionDate = "2013-05-07 13:46:00"
private val oldVersionDateVariant = "2021-03-21 10:32:00.000000"
private val newVersionDateVariant = "2022-12-24T17:00:08+00"
private val newVersionDate = "2013-04-07T13:43:00+01:00"
private val newVersionDate2 = "2013-04-07T13:43:00-01:00"
private val oldVersionDate = "2013-05-07 13:46:00"
private val oldVersionDateVariant = "2021-03-21 10:32:00.000000"
@Test
fun new_version_date_should_be_parsed() {
val date = newVersionDate.toParsedDate()
val expected =
LocalDateTime(2013, 4, 7, 13, 43, 0, 0).toInstant(TimeZone.currentSystemDefault())
LocalDateTime(2013, 4, 7, 13, 43, 0, 0)
.toInstant(TimeZone.currentSystemDefault())
.toEpochMilliseconds()
assertEquals(expected, date)
}
@Test
fun new_version_date2_should_be_parsed() {
val date = newVersionDate2.toParsedDate()
val expected =
LocalDateTime(2013, 4, 7, 13, 43, 0, 0).toInstant(TimeZone.currentSystemDefault())
LocalDateTime(2013, 4, 7, 13, 43, 0, 0)
.toInstant(TimeZone.currentSystemDefault())
.toEpochMilliseconds()
assertEquals(expected, date)
@ -38,7 +40,8 @@ class DatesTest {
fun old_version_date_should_be_parsed() {
val date = oldVersionDate.toParsedDate()
val expected =
LocalDateTime(2013, 5, 7, 13, 46, 0, 0).toInstant(TimeZone.currentSystemDefault())
LocalDateTime(2013, 5, 7, 13, 46, 0, 0)
.toInstant(TimeZone.currentSystemDefault())
.toEpochMilliseconds()
assertEquals(expected, date)
@ -48,7 +51,8 @@ class DatesTest {
fun old_version_variant_date_should_be_parsed() {
val date = oldVersionDateVariant.toParsedDate()
val expected =
LocalDateTime(2021, 3, 21, 10, 32, 0, 0).toInstant(TimeZone.currentSystemDefault())
LocalDateTime(2021, 3, 21, 10, 32, 0, 0)
.toInstant(TimeZone.currentSystemDefault())
.toEpochMilliseconds()
assertEquals(expected, date)
@ -58,7 +62,8 @@ class DatesTest {
fun new_version_variant_date_should_be_parsed() {
val date = newVersionDateVariant.toParsedDate()
val expected =
LocalDateTime(2022, 12, 24, 17, 0, 8, 0).toInstant(TimeZone.currentSystemDefault())
LocalDateTime(2022, 12, 24, 17, 0, 8, 0)
.toInstant(TimeZone.currentSystemDefault())
.toEpochMilliseconds()
assertEquals(expected, date)

View File

@ -4,7 +4,5 @@ import app.cash.sqldelight.db.SqlDriver
import app.cash.sqldelight.driver.native.NativeSqliteDriver
actual class DriverFactory {
actual fun createDriver(): SqlDriver {
return NativeSqliteDriver(ReaderForSelfossDB.Schema, "ReaderForSelfossV2-IOS.db")
}
}
actual fun createDriver(): SqlDriver = NativeSqliteDriver(ReaderForSelfossDB.Schema, "ReaderForSelfossV2-IOS.db")
}

View File

@ -0,0 +1,7 @@
package bou.amine.apps.readerforselfossv2.rest
import io.ktor.client.engine.cio.CIOEngineConfig
@Suppress("detekt:EmptyFunctionBlock")
actual fun setupInsecureHttpEngine(config: CIOEngineConfig) {
}

View File

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

View File

@ -1,9 +1,10 @@
package bou.amine.apps.readerforselfossv2.utils
@Suppress("detekt:UtilityClassWithPublicConstructor")
actual class DateUtils {
actual companion object {
actual fun parseRelativeDate(dateString: String): String {
TODO("Not yet implemented")
}
}
}
}

View File

@ -1,9 +1,10 @@
package bou.amine.apps.readerforselfossv2.utils
@Suppress("detekt:UtilityClassWithPublicConstructor")
actual class DateUtils actual constructor() {
actual companion object {
actual fun parseRelativeDate(dateString: String): String {
TODO("Not yet implemented")
}
}
}
}

View File

@ -4,7 +4,5 @@ import app.cash.sqldelight.db.SqlDriver
import app.cash.sqldelight.driver.native.NativeSqliteDriver
actual class DriverFactory {
actual fun createDriver(): SqlDriver {
return NativeSqliteDriver(ReaderForSelfossDB.Schema, "ReaderForSelfossV2-IOS.db")
}
}
actual fun createDriver(): SqlDriver = NativeSqliteDriver(ReaderForSelfossDB.Schema, "ReaderForSelfossV2-IOS.db")
}

View File

@ -0,0 +1,7 @@
package bou.amine.apps.readerforselfossv2.rest
import io.ktor.client.engine.cio.CIOEngineConfig
@Suppress("detekt:EmptyFunctionBlock")
actual fun setupInsecureHttpEngine(config: CIOEngineConfig) {
}

View File

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

View File

@ -1,9 +1,10 @@
package bou.amine.apps.readerforselfossv2.utils
@Suppress("detekt:UtilityClassWithPublicConstructor")
actual class DateUtils {
actual companion object {
actual fun parseRelativeDate(dateString: String): String {
TODO("Not yet implemented")
}
}
}
}