Compare commits
No commits in common. "8ea46e146b060d990232b04e4b307896ade6ea65" and "325f103417142c2c7eccc547ec844a9037a78f05" have entirely different histories.
8ea46e146b
...
325f103417
@ -142,6 +142,12 @@ dependencies {
|
|||||||
implementation("com.mikepenz:aboutlibraries-core:10.5.1")
|
implementation("com.mikepenz:aboutlibraries-core:10.5.1")
|
||||||
implementation("com.mikepenz:aboutlibraries:10.5.1")
|
implementation("com.mikepenz:aboutlibraries:10.5.1")
|
||||||
|
|
||||||
|
// Retrofit + http logging + okhttp
|
||||||
|
implementation("com.squareup.retrofit2:retrofit:2.9.0")
|
||||||
|
implementation("com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.3")
|
||||||
|
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
|
||||||
|
implementation("com.burgstaller:okhttp-digest:2.5")
|
||||||
|
|
||||||
// Material-ish things
|
// Material-ish things
|
||||||
implementation("com.ashokvarma.android:bottom-navigation-bar:2.2.0")
|
implementation("com.ashokvarma.android:bottom-navigation-bar:2.2.0")
|
||||||
implementation("com.amulyakhare:com.amulyakhare.textdrawable:1.0.1")
|
implementation("com.amulyakhare:com.amulyakhare.textdrawable:1.0.1")
|
||||||
|
7
androidApp/proguard-rules.pro
vendored
7
androidApp/proguard-rules.pro
vendored
@ -30,8 +30,15 @@
|
|||||||
<fields>;
|
<fields>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-dontwarn okio.**
|
||||||
|
-dontwarn retrofit2.Platform$Java8
|
||||||
|
-keep class retrofit.** { *; }
|
||||||
|
-keepclasseswithmembers class * {
|
||||||
|
@retrofit.http.* <methods>;
|
||||||
|
}
|
||||||
-keepattributes *Annotation*,Signature
|
-keepattributes *Annotation*,Signature
|
||||||
-keepattributes Exceptions
|
-keepattributes Exceptions
|
||||||
|
-dontwarn okio.**
|
||||||
-dontwarn javax.annotation.Nullable
|
-dontwarn javax.annotation.Nullable
|
||||||
-dontwarn javax.annotation.ParametersAreNonnullByDefault
|
-dontwarn javax.annotation.ParametersAreNonnullByDefault
|
||||||
|
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.android.api.mercury
|
||||||
|
|
||||||
|
import com.google.gson.GsonBuilder
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
|
import retrofit2.Call
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
import retrofit2.converter.gson.GsonConverterFactory
|
||||||
|
|
||||||
|
class MercuryApi() {
|
||||||
|
private val service: MercuryService
|
||||||
|
|
||||||
|
init {
|
||||||
|
|
||||||
|
val interceptor = HttpLoggingInterceptor()
|
||||||
|
interceptor.level = HttpLoggingInterceptor.Level.NONE
|
||||||
|
val client = OkHttpClient.Builder().addInterceptor(interceptor).build()
|
||||||
|
|
||||||
|
val gson = GsonBuilder()
|
||||||
|
.setLenient()
|
||||||
|
.create()
|
||||||
|
val retrofit =
|
||||||
|
Retrofit
|
||||||
|
.Builder()
|
||||||
|
.baseUrl("https://www.amine-louveau.fr")
|
||||||
|
.client(client)
|
||||||
|
.addConverterFactory(GsonConverterFactory.create(gson))
|
||||||
|
.build()
|
||||||
|
service = retrofit.create(MercuryService::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseUrl(url: String): Call<ParsedContent> {
|
||||||
|
return service.parseUrl(url)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.android.api.mercury
|
||||||
|
|
||||||
|
import android.os.Parcel
|
||||||
|
import android.os.Parcelable
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
class ParsedContent(
|
||||||
|
@SerializedName("title") val title: String,
|
||||||
|
@SerializedName("content") val content: String?,
|
||||||
|
@SerializedName("date_published") val date_published: String,
|
||||||
|
@SerializedName("lead_image_url") val lead_image_url: String?,
|
||||||
|
@SerializedName("dek") val dek: String,
|
||||||
|
@SerializedName("url") val url: String,
|
||||||
|
@SerializedName("domain") val domain: String,
|
||||||
|
@SerializedName("excerpt") val excerpt: String,
|
||||||
|
@SerializedName("total_pages") val total_pages: Int,
|
||||||
|
@SerializedName("rendered_pages") val rendered_pages: Int,
|
||||||
|
@SerializedName("next_page_url") val next_page_url: String
|
||||||
|
) : Parcelable {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmField
|
||||||
|
val CREATOR: Parcelable.Creator<ParsedContent> =
|
||||||
|
object : Parcelable.Creator<ParsedContent> {
|
||||||
|
override fun createFromParcel(source: Parcel): ParsedContent = ParsedContent(source)
|
||||||
|
override fun newArray(size: Int): Array<ParsedContent?> = arrayOfNulls(size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(source: Parcel) : this(
|
||||||
|
title = source.readString().orEmpty(),
|
||||||
|
content = source.readString(),
|
||||||
|
date_published = source.readString().orEmpty(),
|
||||||
|
lead_image_url = source.readString(),
|
||||||
|
dek = source.readString().orEmpty(),
|
||||||
|
url = source.readString().orEmpty(),
|
||||||
|
domain = source.readString().orEmpty(),
|
||||||
|
excerpt = source.readString().orEmpty(),
|
||||||
|
total_pages = source.readInt(),
|
||||||
|
rendered_pages = source.readInt(),
|
||||||
|
next_page_url = source.readString().orEmpty()
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun describeContents() = 0
|
||||||
|
|
||||||
|
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||||
|
dest.writeString(title)
|
||||||
|
dest.writeString(content)
|
||||||
|
dest.writeString(date_published)
|
||||||
|
dest.writeString(lead_image_url)
|
||||||
|
dest.writeString(dek)
|
||||||
|
dest.writeString(url)
|
||||||
|
dest.writeString(domain)
|
||||||
|
dest.writeString(excerpt)
|
||||||
|
dest.writeInt(total_pages)
|
||||||
|
dest.writeInt(rendered_pages)
|
||||||
|
dest.writeString(next_page_url)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.android.api.mercury
|
||||||
|
|
||||||
|
import retrofit2.Call
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.Query
|
||||||
|
|
||||||
|
interface MercuryService {
|
||||||
|
@GET("parser.php")
|
||||||
|
fun parseUrl(@Query("link") link: String): Call<ParsedContent>
|
||||||
|
}
|
@ -21,6 +21,8 @@ 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
|
||||||
import bou.amine.apps.readerforselfossv2.android.R
|
import bou.amine.apps.readerforselfossv2.android.R
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.api.mercury.MercuryApi
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.api.mercury.ParsedContent
|
||||||
import bou.amine.apps.readerforselfossv2.android.databinding.FragmentArticleBinding
|
import bou.amine.apps.readerforselfossv2.android.databinding.FragmentArticleBinding
|
||||||
import bou.amine.apps.readerforselfossv2.android.model.ParecelableItem
|
import bou.amine.apps.readerforselfossv2.android.model.ParecelableItem
|
||||||
import bou.amine.apps.readerforselfossv2.android.model.toModel
|
import bou.amine.apps.readerforselfossv2.android.model.toModel
|
||||||
@ -30,7 +32,6 @@ import bou.amine.apps.readerforselfossv2.android.utils.openInBrowserAsNewTask
|
|||||||
import bou.amine.apps.readerforselfossv2.android.utils.shareLink
|
import bou.amine.apps.readerforselfossv2.android.utils.shareLink
|
||||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
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.rest.MercuryApi
|
|
||||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
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.getImages
|
import bou.amine.apps.readerforselfossv2.utils.getImages
|
||||||
@ -48,6 +49,9 @@ 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
|
||||||
import org.kodein.di.instance
|
import org.kodein.di.instance
|
||||||
|
import retrofit2.Call
|
||||||
|
import retrofit2.Callback
|
||||||
|
import retrofit2.Response
|
||||||
import java.net.MalformedURLException
|
import java.net.MalformedURLException
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@ -77,9 +81,6 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
private var font = ""
|
private var font = ""
|
||||||
private var staticBar = false
|
private var staticBar = false
|
||||||
|
|
||||||
private val mercuryApi : MercuryApi by instance()
|
|
||||||
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
@ -248,79 +249,88 @@ 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
|
||||||
|
val parser = MercuryApi()
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
parser.parseUrl(url).enqueue(
|
||||||
val response = mercuryApi.query(url)
|
object : Callback<ParsedContent> {
|
||||||
if (response.success) {
|
override fun onResponse(
|
||||||
try {
|
call: Call<ParsedContent>,
|
||||||
if (response.data != null && response.data!!.content != null && !response.data!!.content.isNullOrEmpty()) {
|
response: Response<ParsedContent>
|
||||||
try {
|
) {
|
||||||
binding.titleView.text = response.data!!.title
|
// TODO: clean all the following after finding the mercury content issue
|
||||||
if (typeface != null) {
|
try {
|
||||||
binding.titleView.typeface = typeface
|
if (response.body() != null && response.body()!!.content != null && !response.body()!!.content.isNullOrEmpty()) {
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
// Note: Mercury may return relative urls... If it does the url val will not be changed.
|
binding.titleView.text = response.body()!!.title
|
||||||
URL(response.data!!.url)
|
if (typeface != null) {
|
||||||
url = response.data!!.url
|
binding.titleView.typeface = typeface
|
||||||
} catch (e: MalformedURLException) {
|
|
||||||
// Mercury returned a relative url. We do nothing.
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
contentText = response.data!!.content.orEmpty()
|
|
||||||
htmlToWebview()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (response.data!!.lead_image_url != null && !response.data!!.lead_image_url.isNullOrEmpty() && context != null) {
|
|
||||||
binding.imageView.visibility = View.VISIBLE
|
|
||||||
try {
|
|
||||||
Glide
|
|
||||||
.with(requireContext())
|
|
||||||
.asBitmap()
|
|
||||||
.load(
|
|
||||||
response.data!!.lead_image_url.orEmpty()
|
|
||||||
)
|
|
||||||
.apply(RequestOptions.fitCenterTransform())
|
|
||||||
.into(binding.imageView)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
}
|
}
|
||||||
} else {
|
try {
|
||||||
binding.imageView.visibility = View.GONE
|
// Note: Mercury may return relative urls... If it does the url val will not be changed.
|
||||||
|
URL(response.body()!!.url)
|
||||||
|
url = response.body()!!.url
|
||||||
|
} catch (e: MalformedURLException) {
|
||||||
|
// Mercury returned a relative url. We do nothing.
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
|
||||||
if (context != null) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
binding.nestedScrollView.scrollTo(0, 0)
|
contentText = response.body()!!.content.orEmpty()
|
||||||
|
htmlToWebview()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
}
|
||||||
|
|
||||||
binding.progressBar.visibility = View.GONE
|
try {
|
||||||
} catch (e: Exception) {
|
if (response.body()!!.lead_image_url != null && !response.body()!!.lead_image_url.isNullOrEmpty() && context != null) {
|
||||||
if (context != null) {
|
binding.imageView.visibility = View.VISIBLE
|
||||||
|
try {
|
||||||
|
Glide
|
||||||
|
.with(requireContext())
|
||||||
|
.asBitmap()
|
||||||
|
.load(
|
||||||
|
response.body()!!.lead_image_url.orEmpty()
|
||||||
|
)
|
||||||
|
.apply(RequestOptions.fitCenterTransform())
|
||||||
|
.into(binding.imageView)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
binding.imageView.visibility = View.GONE
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
if (context != null) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
binding.nestedScrollView.scrollTo(0, 0)
|
||||||
|
|
||||||
|
binding.progressBar.visibility = View.GONE
|
||||||
|
} catch (e: Exception) {
|
||||||
|
if (context != null) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
openInBrowserAfterFailing()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
if (context != null) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} catch (e: Exception) {
|
||||||
try {
|
if (context != null) {
|
||||||
openInBrowserAfterFailing()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
if (context != null) {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
|
||||||
if (context != null) {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
openInBrowserAfterFailing()
|
override fun onFailure(
|
||||||
|
call: Call<ParsedContent>,
|
||||||
|
t: Throwable
|
||||||
|
) = openInBrowserAfterFailing()
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ package bou.amine.apps.readerforselfossv2.android.model
|
|||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
fun SelfossModel.Item.toParcelable() : ParecelableItem =
|
fun SelfossModel.Item.toParcelable() : ParecelableItem =
|
||||||
ParecelableItem(
|
ParecelableItem(
|
||||||
@ -33,17 +34,17 @@ fun ParecelableItem.toModel() : SelfossModel.Item =
|
|||||||
this.tags.split(",")
|
this.tags.split(",")
|
||||||
)
|
)
|
||||||
data class ParecelableItem(
|
data class ParecelableItem(
|
||||||
val id: Int,
|
@SerializedName("id") val id: Int,
|
||||||
val datetime: String,
|
@SerializedName("datetime") val datetime: String,
|
||||||
val title: String,
|
@SerializedName("title") val title: String,
|
||||||
val content: String,
|
@SerializedName("content") val content: String,
|
||||||
var unread: Boolean,
|
@SerializedName("unread") var unread: Boolean,
|
||||||
var starred: Boolean,
|
@SerializedName("starred") var starred: Boolean,
|
||||||
val thumbnail: String?,
|
@SerializedName("thumbnail") val thumbnail: String?,
|
||||||
val icon: String?,
|
@SerializedName("icon") val icon: String?,
|
||||||
val link: String,
|
@SerializedName("link") val link: String,
|
||||||
val sourcetitle: String,
|
@SerializedName("sourcetitle") val sourcetitle: String,
|
||||||
val tags: String
|
@SerializedName("tags") val tags: String
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
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
|
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
|
||||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||||
import org.kodein.di.DI
|
import org.kodein.di.DI
|
||||||
@ -11,5 +10,4 @@ import org.kodein.di.singleton
|
|||||||
val networkModule by DI.Module {
|
val networkModule by DI.Module {
|
||||||
bind<AppSettingsService>() with singleton { AppSettingsService() }
|
bind<AppSettingsService>() with singleton { AppSettingsService() }
|
||||||
bind<SelfossApi>() with singleton { SelfossApi(instance()) }
|
bind<SelfossApi>() with singleton { SelfossApi(instance()) }
|
||||||
bind<MercuryApi>() with singleton { MercuryApi() }
|
|
||||||
}
|
}
|
@ -1,157 +0,0 @@
|
|||||||
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.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 {
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
class ParsedContent(
|
|
||||||
val title: String,
|
|
||||||
val content: String?,
|
|
||||||
val lead_image_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("&url=")) {
|
|
||||||
link.substringAfter("&url=")
|
|
||||||
} else {
|
|
||||||
this.link.replace("&", "&")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.link.replace("&", "&")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.model
|
|
||||||
|
|
||||||
import io.ktor.client.call.*
|
|
||||||
import io.ktor.client.statement.*
|
|
||||||
import io.ktor.http.*
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
class SuccessResponse(val success: Boolean) {
|
|
||||||
val isSuccess: Boolean
|
|
||||||
get() = success
|
|
||||||
}
|
|
||||||
|
|
||||||
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> error(): StatusAndData<T> {
|
|
||||||
return StatusAndData(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
@ -20,6 +20,12 @@ class SelfossModel {
|
|||||||
val unread: Int
|
val unread: Int
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class SuccessResponse(val success: Boolean) {
|
||||||
|
val isSuccess: Boolean
|
||||||
|
get() = success
|
||||||
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class Stats(
|
class Stats(
|
||||||
val total: Int,
|
val total: Int,
|
||||||
@ -146,4 +152,16 @@ class SelfossModel {
|
|||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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> error(): StatusAndData<T> {
|
||||||
|
return StatusAndData(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ package bou.amine.apps.readerforselfossv2.repository
|
|||||||
import bou.amine.apps.readerforselfossv2.dao.*
|
import bou.amine.apps.readerforselfossv2.dao.*
|
||||||
import bou.amine.apps.readerforselfossv2.model.NetworkUnavailableException
|
import bou.amine.apps.readerforselfossv2.model.NetworkUnavailableException
|
||||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
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.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.*
|
||||||
@ -41,7 +40,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
|
|||||||
|
|
||||||
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: SelfossModel.StatusAndData<List<SelfossModel.Item>> = SelfossModel.StatusAndData.error()
|
||||||
var fromDB = false
|
var fromDB = false
|
||||||
if (isNetworkAvailable()) {
|
if (isNetworkAvailable()) {
|
||||||
fetchedItems = api.getItems(
|
fetchedItems = api.getItems(
|
||||||
@ -66,7 +65,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
|
|||||||
if (sourceFilter != null) {
|
if (sourceFilter != null) {
|
||||||
dbItems = dbItems.filter { it.sourcetitle == sourceFilter!!.title }
|
dbItems = dbItems.filter { it.sourcetitle == sourceFilter!!.title }
|
||||||
}
|
}
|
||||||
fetchedItems = StatusAndData.succes(
|
fetchedItems = SelfossModel.StatusAndData.succes(
|
||||||
dbItems.map { it.toView() }
|
dbItems.map { it.toView() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -82,7 +81,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getOlderItems(): ArrayList<SelfossModel.Item> {
|
suspend fun getOlderItems(): ArrayList<SelfossModel.Item> {
|
||||||
var fetchedItems: StatusAndData<List<SelfossModel.Item>> = StatusAndData.error()
|
var fetchedItems: SelfossModel.StatusAndData<List<SelfossModel.Item>> = SelfossModel.StatusAndData.error()
|
||||||
if (isNetworkAvailable()) {
|
if (isNetworkAvailable()) {
|
||||||
val offset = items.size
|
val offset = items.size
|
||||||
fetchedItems = api.getItems(
|
fetchedItems = api.getItems(
|
||||||
|
@ -1,43 +0,0 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.rest
|
|
||||||
|
|
||||||
import bou.amine.apps.readerforselfossv2.model.*
|
|
||||||
import io.github.aakira.napier.Napier
|
|
||||||
import io.ktor.client.*
|
|
||||||
import io.ktor.client.plugins.cache.*
|
|
||||||
import io.ktor.client.plugins.contentnegotiation.*
|
|
||||||
import io.ktor.client.plugins.logging.*
|
|
||||||
import io.ktor.client.request.*
|
|
||||||
import io.ktor.serialization.kotlinx.json.*
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
|
|
||||||
class MercuryApi() {
|
|
||||||
|
|
||||||
var client = createHttpClient()
|
|
||||||
|
|
||||||
private fun createHttpClient(): HttpClient {
|
|
||||||
return HttpClient {
|
|
||||||
install(ContentNegotiation) {
|
|
||||||
install(HttpCache)
|
|
||||||
json(Json {
|
|
||||||
prettyPrint = true
|
|
||||||
isLenient = true
|
|
||||||
ignoreUnknownKeys = true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
install(Logging) {
|
|
||||||
logger = object : Logger {
|
|
||||||
override fun log(message: String) {
|
|
||||||
Napier.d(message, tag = "LogMercuryCalls")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
level = LogLevel.INFO
|
|
||||||
}
|
|
||||||
expectSuccess = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun query(url: String): StatusAndData<MercuryModel.ParsedContent> =
|
|
||||||
bodyOrFailure(client.get("https://amine-louveau.fr/parser.php") {
|
|
||||||
parameter("link", url)
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,6 +1,6 @@
|
|||||||
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.service.AppSettingsService
|
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||||
import io.ktor.client.*
|
import io.ktor.client.*
|
||||||
import io.ktor.client.call.*
|
import io.ktor.client.call.*
|
||||||
@ -66,7 +66,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
client = createHttpClient()
|
client = createHttpClient()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun login(): SuccessResponse =
|
suspend fun login(): SelfossModel.SuccessResponse =
|
||||||
maybeResponse(client.get(url("/login")) {
|
maybeResponse(client.get(url("/login")) {
|
||||||
parameter("username", appSettingsService.getUserName())
|
parameter("username", appSettingsService.getUserName())
|
||||||
parameter("password", appSettingsService.getPassword())
|
parameter("password", appSettingsService.getPassword())
|
||||||
@ -80,7 +80,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
search: String?,
|
search: String?,
|
||||||
updatedSince: String?,
|
updatedSince: String?,
|
||||||
items: Int? = null
|
items: Int? = null
|
||||||
): StatusAndData<List<SelfossModel.Item>> =
|
): SelfossModel.StatusAndData<List<SelfossModel.Item>> =
|
||||||
bodyOrFailure(client.get(url("/items")) {
|
bodyOrFailure(client.get(url("/items")) {
|
||||||
parameter("username", appSettingsService.getUserName())
|
parameter("username", appSettingsService.getUserName())
|
||||||
parameter("password", appSettingsService.getPassword())
|
parameter("password", appSettingsService.getPassword())
|
||||||
@ -93,64 +93,64 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
parameter("offset", offset)
|
parameter("offset", offset)
|
||||||
})
|
})
|
||||||
|
|
||||||
suspend fun stats(): StatusAndData<SelfossModel.Stats> =
|
suspend fun stats(): SelfossModel.StatusAndData<SelfossModel.Stats> =
|
||||||
bodyOrFailure(client.get(url("/stats")) {
|
bodyOrFailure(client.get(url("/stats")) {
|
||||||
parameter("username", appSettingsService.getUserName())
|
parameter("username", appSettingsService.getUserName())
|
||||||
parameter("password", appSettingsService.getPassword())
|
parameter("password", appSettingsService.getPassword())
|
||||||
})
|
})
|
||||||
|
|
||||||
suspend fun tags(): StatusAndData<List<SelfossModel.Tag>> =
|
suspend fun tags(): SelfossModel.StatusAndData<List<SelfossModel.Tag>> =
|
||||||
bodyOrFailure(client.get(url("/tags")) {
|
bodyOrFailure(client.get(url("/tags")) {
|
||||||
parameter("username", appSettingsService.getUserName())
|
parameter("username", appSettingsService.getUserName())
|
||||||
parameter("password", appSettingsService.getPassword())
|
parameter("password", appSettingsService.getPassword())
|
||||||
})
|
})
|
||||||
|
|
||||||
suspend fun update(): StatusAndData<String> =
|
suspend fun update(): SelfossModel.StatusAndData<String> =
|
||||||
bodyOrFailure(client.get(url("/update")) {
|
bodyOrFailure(client.get(url("/update")) {
|
||||||
parameter("username", appSettingsService.getUserName())
|
parameter("username", appSettingsService.getUserName())
|
||||||
parameter("password", appSettingsService.getPassword())
|
parameter("password", appSettingsService.getPassword())
|
||||||
})
|
})
|
||||||
|
|
||||||
suspend fun spouts(): StatusAndData<Map<String, SelfossModel.Spout>> =
|
suspend fun spouts(): SelfossModel.StatusAndData<Map<String, SelfossModel.Spout>> =
|
||||||
bodyOrFailure(client.get(url("/sources/spouts")) {
|
bodyOrFailure(client.get(url("/sources/spouts")) {
|
||||||
parameter("username", appSettingsService.getUserName())
|
parameter("username", appSettingsService.getUserName())
|
||||||
parameter("password", appSettingsService.getPassword())
|
parameter("password", appSettingsService.getPassword())
|
||||||
})
|
})
|
||||||
|
|
||||||
suspend fun sources(): StatusAndData<ArrayList<SelfossModel.Source>> =
|
suspend fun sources(): SelfossModel.StatusAndData<ArrayList<SelfossModel.Source>> =
|
||||||
bodyOrFailure(client.get(url("/sources/list")) {
|
bodyOrFailure(client.get(url("/sources/list")) {
|
||||||
parameter("username", appSettingsService.getUserName())
|
parameter("username", appSettingsService.getUserName())
|
||||||
parameter("password", appSettingsService.getPassword())
|
parameter("password", appSettingsService.getPassword())
|
||||||
})
|
})
|
||||||
|
|
||||||
suspend fun version(): StatusAndData<SelfossModel.ApiVersion> =
|
suspend fun version(): SelfossModel.StatusAndData<SelfossModel.ApiVersion> =
|
||||||
bodyOrFailure(client.get(url("/api/about")))
|
bodyOrFailure(client.get(url("/api/about")))
|
||||||
|
|
||||||
suspend fun markAsRead(id: String): SuccessResponse =
|
suspend fun markAsRead(id: String): SelfossModel.SuccessResponse =
|
||||||
maybeResponse(client.post(url("/mark/$id")) {
|
maybeResponse(client.post(url("/mark/$id")) {
|
||||||
parameter("username", appSettingsService.getUserName())
|
parameter("username", appSettingsService.getUserName())
|
||||||
parameter("password", appSettingsService.getPassword())
|
parameter("password", appSettingsService.getPassword())
|
||||||
})
|
})
|
||||||
|
|
||||||
suspend fun unmarkAsRead(id: String): SuccessResponse =
|
suspend fun unmarkAsRead(id: String): SelfossModel.SuccessResponse =
|
||||||
maybeResponse(client.post(url("/unmark/$id")) {
|
maybeResponse(client.post(url("/unmark/$id")) {
|
||||||
parameter("username", appSettingsService.getUserName())
|
parameter("username", appSettingsService.getUserName())
|
||||||
parameter("password", appSettingsService.getPassword())
|
parameter("password", appSettingsService.getPassword())
|
||||||
})
|
})
|
||||||
|
|
||||||
suspend fun starr(id: String): SuccessResponse =
|
suspend fun starr(id: String): SelfossModel.SuccessResponse =
|
||||||
maybeResponse(client.post(url("/starr/$id")) {
|
maybeResponse(client.post(url("/starr/$id")) {
|
||||||
parameter("username", appSettingsService.getUserName())
|
parameter("username", appSettingsService.getUserName())
|
||||||
parameter("password", appSettingsService.getPassword())
|
parameter("password", appSettingsService.getPassword())
|
||||||
})
|
})
|
||||||
|
|
||||||
suspend fun unstarr(id: String): SuccessResponse =
|
suspend fun unstarr(id: String): SelfossModel.SuccessResponse =
|
||||||
maybeResponse(client.post(url("/unstarr/$id")) {
|
maybeResponse(client.post(url("/unstarr/$id")) {
|
||||||
parameter("username", appSettingsService.getUserName())
|
parameter("username", appSettingsService.getUserName())
|
||||||
parameter("password", appSettingsService.getPassword())
|
parameter("password", appSettingsService.getPassword())
|
||||||
})
|
})
|
||||||
|
|
||||||
suspend fun markAllAsRead(ids: List<String>): SuccessResponse =
|
suspend fun markAllAsRead(ids: List<String>): SelfossModel.SuccessResponse =
|
||||||
maybeResponse(client.submitForm(
|
maybeResponse(client.submitForm(
|
||||||
url = url("/mark"),
|
url = url("/mark"),
|
||||||
formParameters = Parameters.build {
|
formParameters = Parameters.build {
|
||||||
@ -167,7 +167,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
tags: String,
|
tags: String,
|
||||||
filter: String,
|
filter: String,
|
||||||
version: Int
|
version: Int
|
||||||
): SuccessResponse =
|
): SelfossModel.SuccessResponse =
|
||||||
maybeResponse(
|
maybeResponse(
|
||||||
if (version > 1) {
|
if (version > 1) {
|
||||||
createSource2(title, url, spout, tags, filter)
|
createSource2(title, url, spout, tags, filter)
|
||||||
@ -212,9 +212,25 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
suspend fun deleteSource(id: Int): SuccessResponse =
|
suspend fun deleteSource(id: Int): SelfossModel.SuccessResponse =
|
||||||
maybeResponse(client.delete(url("/source/$id")) {
|
maybeResponse(client.delete(url("/source/$id")) {
|
||||||
parameter("username", appSettingsService.getUserName())
|
parameter("username", appSettingsService.getUserName())
|
||||||
parameter("password", appSettingsService.getPassword())
|
parameter("password", appSettingsService.getPassword())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
suspend fun maybeResponse(r: HttpResponse): SelfossModel.SuccessResponse {
|
||||||
|
return if (r.status.isSuccess()) {
|
||||||
|
r.body()
|
||||||
|
} else {
|
||||||
|
SelfossModel.SuccessResponse(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend inline fun <reified T> bodyOrFailure(r: HttpResponse): SelfossModel.StatusAndData<T> {
|
||||||
|
return if (r.status.isSuccess()) {
|
||||||
|
SelfossModel.StatusAndData.succes(r.body())
|
||||||
|
} else {
|
||||||
|
SelfossModel.StatusAndData.error()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user