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