Commit 331fb735 authored by Christophe Henry's avatar Christophe Henry

Drop Kotlin serialization library in favor of Jackson in order to be included in F-droid

parent b32a8861
......@@ -45,20 +45,18 @@ android {
dependencies {
def lifecycle_version = "2.0.0"
def room_version = "2.1.0-alpha04"
def fuel_version = "1.16.0"
def fuel_version = "2.0.1"
def jackson_version = "2.9.7"
def espresso_version = "3.1.1"
def test_runnner_version = "1.1.1"
def promise_version = "3.3.0"
def android_support_version = "28.0.0"
def android_navigation = "1.0.0-rc02"
configurations {
all*.exclude group: 'com.google.guava', module: 'listenablefuture'
}
implementation "androidx.legacy:legacy-support-v4:1.0.0"
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation fileTree(include: ['*.jar'], dir: 'libs')
// Kotlin stuff
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
......@@ -72,11 +70,11 @@ dependencies {
implementation "com.android.support:support-compat:$android_support_version"
// AndroidX layout
implementation "androidx.appcompat:appcompat:1.1.0-alpha02"
implementation "androidx.core:core-ktx:1.1.0-alpha03"
implementation "com.google.android.material:material:1.0.0-beta01"
implementation "androidx.constraintlayout:constraintlayout:1.1.3"
implementation "androidx.recyclerview:recyclerview:1.0.0"
implementation 'androidx.appcompat:appcompat:1.1.0-alpha02'
implementation 'androidx.core:core-ktx:1.1.0-alpha03'
implementation 'com.google.android.material:material:1.0.0-beta01'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.recyclerview:recyclerview:1.0.0'
// ViewModel and LiveData
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.0.0'
......@@ -93,10 +91,11 @@ dependencies {
implementation "androidx.room:room-coroutines:$room_version"
testImplementation "androidx.room:room-testing:$room_version"
// HTTP and promises
implementation "com.github.kittinunf.fuel:fuel:$fuel_version"
implementation "com.github.kittinunf.fuel:fuel-android:$fuel_version"
implementation "com.github.kittinunf.fuel:fuel-kotlinx-serialization:$fuel_version"
implementation "com.github.kittinunf.fuel:fuel-jackson:$fuel_version"
implementation "com.fasterxml.jackson.datatype:jackson-datatype-joda:$jackson_version"
implementation "nl.komponents.kovenant:kovenant:$promise_version"
implementation "nl.komponents.kovenant:kovenant-android:$promise_version"
......@@ -105,16 +104,15 @@ dependencies {
implementation "android.arch.navigation:navigation-ui-ktx:$android_navigation"
// Utils
implementation "org.apache.commons:commons-text:1.4"
implementation "joda-time:joda-time:2.10.1"
implementation 'org.apache.commons:commons-text:1.4'
implementation 'joda-time:joda-time:2.10.1'
// Tests
testImplementation "junit:junit:4.12"
testImplementation 'junit:junit:4.12'
androidTestImplementation "androidx.test:runner:$test_runnner_version"
androidTestImplementation("androidx.test.espresso:espresso-core:$espresso_version") {
exclude group: "com.google.code.findbugs"
}
androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_version"
// Debug
// implementation "com.facebook.stetho:stetho:1.5.0"
}
......@@ -24,14 +24,14 @@ class Api(private val account: Account) {
Fuel
.getJson(endpoints.subscriptionEndpoint)
.authentify(this.account)
.promise(SubscriptionsHandler.serializer())
.promise<SubscriptionsHandler>()
.then {it.subscriptions}
fun getUnreadCount(): Promise<UnreadCountsHandler, Exception> =
Fuel
.getJson(endpoints.unreadCountEndpoint)
.authentify(this.account)
.promise(UnreadCountsHandler.serializer())
.promise()
fun getStreamItems(id: String): Promise<String, Exception> =
Fuel
......@@ -43,7 +43,7 @@ class Api(private val account: Account) {
Fuel
.getJson(endpoints.streamContentsEndpoint(id))
.authentify(this.account)
.promise(ContentItemsHandler.serializer())
.promise()
fun getTags(): Promise<List<String>, Exception> =
Fuel
......
package fr.chenry.android.freshrss.store.api.models
import kotlinx.serialization.Optional
import kotlinx.serialization.Serializable
@Serializable
data class ContentItemsHandler(
val id: String,
val updated: Long,
val items: ContentItems,
@Optional
val continuation: String = ""
)
typealias ContentItems = List<ContentItem>
@Serializable
data class ContentItem(
val id: String,
val crawlTimeMsec: Long,
......@@ -25,18 +19,14 @@ data class ContentItem(
val categories: List<String>,
val origin: ContentItemOrigin,
val summary: Summary,
@Optional
val author: String = ""
)
@Serializable
data class ContentItemOrigin(
val streamId: String,
val title: String
)
@Serializable
data class Summary(val content: String)
@Serializable
data class Href(val href: String)
\ No newline at end of file
......@@ -2,23 +2,22 @@ package fr.chenry.android.freshrss.store.api.models
import androidx.databinding.BaseObservable
import androidx.databinding.Bindable
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.databind.deser.std.StringDeserializer
import fr.chenry.android.freshrss.BR
import fr.chenry.android.freshrss.utils.escapeHtml4
import fr.chenry.android.freshrss.utils.unescapeHtml4
import kotlinx.serialization.*
import kotlinx.serialization.internal.SerialClassDescImpl
typealias StreamId = String
@Serializable
data class SubscriptionsHandler(val subscriptions: Subscriptions)
typealias Subscriptions = List<Subscription>
@Serializable
data class Subscription(
val id: String,
@Serializable(with = HtmlEntitiesSerializer::class)
@JsonDeserialize(using = HtmlEntitiesDeserializer::class)
val title: String,
val categories: List<SubscriptionCategory>,
val url: String,
......@@ -32,15 +31,11 @@ data class Subscription(
}
}
@Serializable
data class SubscriptionCategory(
val id: String,
val label: String
)
@Serializer(forClass = String::class)
object HtmlEntitiesSerializer: KSerializer<String> {
override val descriptor: SerialDescriptor = SerialClassDescImpl("HtmlEntities")
override fun serialize(output: Encoder, obj: String) = output.encodeString(obj.escapeHtml4())
override fun deserialize(input: Decoder) = input.decodeString().unescapeHtml4()
class HtmlEntitiesDeserializer: StringDeserializer() {
override fun deserialize(p: JsonParser?, ctxt: DeserializationContext?) = super.deserialize(p, ctxt).escapeHtml4()
}
\ No newline at end of file
package fr.chenry.android.freshrss.store.api.models
import kotlinx.serialization.*
import kotlinx.serialization.internal.SerialClassDescImpl
import java.util.*
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.datatype.joda.deser.LocalDateTimeDeserializer
import org.joda.time.DateTimeZone
import org.joda.time.LocalDateTime
@Serializable
data class UnreadCountsHandler(val max: Int, val unreadcounts: List<UnreadCount>)
@Serializable
data class UnreadCount(
val id: String,
val count: Int,
@Serializable(with = TimestampSerializer::class)
val newestItemTimestampUsec: Date
@JsonDeserialize(using = MicroSecTimestampDeserializer::class)
val newestItemTimestampUsec: LocalDateTime
)
@Serializer(forClass = Date::class)
object TimestampSerializer: KSerializer<Date> {
override val descriptor: SerialDescriptor = SerialClassDescImpl("Timestamp")
override fun serialize(output: Encoder, obj: Date) = output.encodeString(obj.time.toString())
override fun deserialize(input: Decoder) = Date(input.decodeString().toLong())
class MicroSecTimestampDeserializer: LocalDateTimeDeserializer() {
override fun deserialize(p: JsonParser?, ctxt: DeserializationContext?): LocalDateTime {
val tz = if(_format.isTimezoneExplicit) _format.timeZone else DateTimeZone.forTimeZone(ctxt?.timeZone)
return LocalDateTime(p?.valueAsString?.toLongOrNull()?.div(1000), tz)
}
}
\ No newline at end of file
package fr.chenry.android.freshrss.utils
import android.util.Log
import fr.chenry.android.freshrss.store.Store
import fr.chenry.android.freshrss.store.database.models.Account
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.datatype.joda.JodaModule
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
import com.github.kittinunf.fuel.Fuel
import com.github.kittinunf.fuel.core.*
import com.github.kittinunf.fuel.serialization.responseObject
import com.github.kittinunf.fuel.jackson.responseObject
import com.github.kittinunf.result.Result
import kotlinx.serialization.*
import kotlinx.serialization.json.JSON
import fr.chenry.android.freshrss.store.Store
import fr.chenry.android.freshrss.store.database.models.Account
import nl.komponents.kovenant.Kovenant.deferred
import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.task
......@@ -18,8 +20,10 @@ import org.apache.commons.text.StringEscapeUtils
import org.apache.commons.text.WordUtils
import kotlin.reflect.full.companionObjectInstance
inline fun <reified T : Any> Request.promiseWithSerializer(loader: ResponseDeserializable<T>? = null): Promise<T, Exception> {
if (loader == null && T::class.companionObjectInstance !is ResponseDeserializable<*>) {
inline fun <reified T: Any> Request.promiseWithSerializer(
loader: ResponseDeserializable<T>? = null
): Promise<T, Exception> {
if(loader == null && T::class.companionObjectInstance !is ResponseDeserializable<*>) {
val exception = IllegalStateException(
"""${T::class.simpleName} must have a companion object implementing
ResponseDeserializable<${T::class.simpleName}> or `loader`
......@@ -30,40 +34,45 @@ inline fun <reified T : Any> Request.promiseWithSerializer(loader: ResponseDeser
@Suppress("UNCHECKED_CAST")
val finalLoader = loader ?: T::class.companionObjectInstance as ResponseDeserializable<T>
return asyncRequest { responseObject(finalLoader) }
return asyncRequest {responseObject(finalLoader)}
}
inline fun <reified T : Any> Request.promise(loader: DeserializationStrategy<T> = T::class.serializer())
= asyncRequest { responseObject(loader, JSON.nonstrict) }
inline fun <reified T: Any> Request.promise() = asyncRequest {
responseObject<T>(
ObjectMapper().registerKotlinModule()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.registerModule(JodaModule())
)
}
fun Request.promiseString() = asyncRequest { responseString() }
fun Request.promiseString() = asyncRequest {responseString()}
fun <T: Any>asyncRequest(callback: () -> Triple<Request, Response, Result<T, FuelError>>): Promise<T, Exception> {
fun <T: Any> asyncRequest(callback: () -> Triple<Request, Response, Result<T, FuelError>>): Promise<T, Exception> {
val deferred = deferred<T, Exception>()
val onSuccess = { triple: Triple<Request, Response, Result<T, FuelError>> ->
val onSuccess = {triple: Triple<Request, Response, Result<T, FuelError>> ->
val (_, _, result) = triple
when (result) {
when(result) {
is Result.Success -> deferred.resolve(result.value)
is Result.Failure -> deferred.reject(result.error)
}
}
if(Store.debugMode) {
task { callback() } successUi(onSuccess) failUi {
task {callback()} successUi (onSuccess) failUi {
Log.e(Request::class.qualifiedName, "HTTP request failed", it)
deferred.reject(it)
}
} else {
task { callback() } success(onSuccess) fail(deferred::reject)
task {callback()} success (onSuccess) fail (deferred::reject)
}
return deferred.promise
}
fun Fuel.Companion.postJson(path: String, parameters: List<Pair<String, Any?>>? = null) =
fun Fuel.postJson(path: String, parameters: List<Pair<String, Any?>>? = null) =
Fuel.post(path, parameters.orEmpty() + ("output" to "json"))
fun Fuel.Companion.getJson(path: String, parameters: List<Pair<String, Any?>>? = null) =
fun Fuel.getJson(path: String, parameters: List<Pair<String, Any?>>? = null) =
Fuel.get(path, parameters.orEmpty() + ("output" to "json"))
fun Request.authentify(account: Account) = this.header("Authorization" to "GoogleLogin auth=${account.SID}")
......@@ -89,12 +98,12 @@ fun String.unescapeHtml4() = StringEscapeUtils.unescapeHtml4(this).orEmpty()
fun String.capitalizeFull() = WordUtils.capitalize(this)
fun String?.nullIfBlank() = if(this.isNullOrBlank()) null else this
fun <V, E>Promise<V, E>.getOrDefault(default: V) = try {
fun <V, E> Promise<V, E>.getOrDefault(default: V) = try {
this.get()
} catch (e: Exception) {
} catch(e: Exception) {
this.w(e)
default
}
fun <T: Any>T?.whenNotNull(body: (T) -> Unit) = if(this !== null) body(this).let{this} else this
fun <T: Any>T?.whenNull(body: () -> Unit) = this ?: body().let {this}
\ No newline at end of file
fun <T: Any> T?.whenNotNull(body: (T) -> Unit) = if(this !== null) body(this).let {this} else this
fun <T: Any> T?.whenNull(body: () -> Unit) = this ?: body().let {this}
\ No newline at end of file
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.3.11'
ext.kotlin_version = '1.3.21'
ext.kotlinx_serialization_version = '0.6.2'
repositories {
google()
maven { url "https://kotlin.bintray.com/kotlinx" }
jcenter()
}
......@@ -14,14 +13,12 @@ buildscript {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0-rc02"
}
}
allprojects {
repositories {
google()
maven { url "https://kotlin.bintray.com/kotlinx" }
jcenter()
}
......@@ -30,3 +27,7 @@ allprojects {
task clean(type: Delete) {
delete rootProject.buildDir
}
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
kotlinOptions.freeCompilerArgs += ["-Xuse-experimental=kotlin.Experimental"]
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment