Commit 3d013434 authored by Christophe Henry's avatar Christophe Henry

Fix unread incomplete unread article list

parent 72ec5e61
......@@ -9,6 +9,7 @@ import fr.chenry.android.freshrss.components.waiting.WaitingFragment
import fr.chenry.android.freshrss.store.*
import fr.chenry.android.freshrss.utils.e
import nl.komponents.kovenant.*
import nl.komponents.kovenant.functional.bind
import nl.komponents.kovenant.ui.failUi
import nl.komponents.kovenant.ui.successUi
......@@ -16,14 +17,12 @@ class MainActivity: AppCompatActivity() {
private val deferred = deferred<Unit, Throwable>()
init {
Store.getSubscriptions() then {
Store.getUnreadCount().get()
} then {
val promises = Store.subscriptions.value!!.keys.map {key -> Store.getStreamContents(key)}
all(promises, cancelOthersOnError = false).get()
} then {
Store.getUnreadItems().get()
} success { deferred.resolve() } fail (deferred::reject)
Store.getSubscriptions()
.bind {Store.getUnreadCount()}
.bind {all(Store.subscriptions.keys.map {Store.getStreamContents(it)}, cancelOthersOnError = false)}
.bind {Store.getUnreadItems()}
.success {deferred.resolve()}
.fail(deferred::reject)
}
override fun onCreate(savedInstanceState: Bundle?) {
......@@ -36,16 +35,14 @@ class MainActivity: AppCompatActivity() {
override fun onStart() {
super.onStart()
deferred.promise successUi {Router.navigate(RouteName.SUBSCRIPTIONS, pushHistory = false)} failUi {this.e(it)}
deferred.promise failUi {this.e(it)}
Router.navigate(RouteName.SUBSCRIPTIONS, pushHistory = false)
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
if(item == null) return super.onOptionsItemSelected(item)
return when(item.itemId) {
android.R.id.home -> {
onBackPressed()
true
}
android.R.id.home -> onBackPressed().let {true}
else -> super.onOptionsItemSelected(item)
}
}
......@@ -62,8 +59,10 @@ class MainActivity: AppCompatActivity() {
val fragmentTransaction = supportFragmentManager
?.beginTransaction()
?.replace(R.id.fragment_container,
Fragment.instantiate(this, to.fragment.qualifiedName, bundle))
?.replace(
R.id.fragment_container,
Fragment.instantiate(this, to.fragment.qualifiedName, bundle)
)
if(to.pushHistory)
fragmentTransaction?.addToBackStack(null)
......
package fr.chenry.android.freshrss.components.subscriptionarticles
import android.view.*
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.RecyclerView
import fr.chenry.android.freshrss.BR
import fr.chenry.android.freshrss.R
import fr.chenry.android.freshrss.databinding.FragmentSubscriptionArticleBinding
import fr.chenry.android.freshrss.store.*
import fr.chenry.android.freshrss.store.dao.store.Article
class RecyclerViewAdapter(private val fragment: SubscriptionArticlesFragment):
RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder>()
{
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding: FragmentSubscriptionArticleBinding
= DataBindingUtil.inflate(layoutInflater, R.layout.fragment_subscription_article, parent, false)
return ViewHolder(binding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val article = fragment.articles[position]
holder.bind(article)
holder.binding.root.setOnClickListener{
Router.navigate(RouteName.SUBSCRIPTION_CONTENT_DETAIL,
mapOf("streamId" to fragment.streamId, "articleId" to article.id))
}
}
override fun getItemCount(): Int = fragment.articles.size
inner class ViewHolder(val binding: FragmentSubscriptionArticleBinding):
RecyclerView.ViewHolder(binding.root) {
fun bind(article: Article) {
binding.setVariable(BR.article, article)
binding.executePendingBindings()
}
}
}
package fr.chenry.android.freshrss.components.subscriptioncontents
package fr.chenry.android.freshrss.components.subscriptionarticles
import android.content.Intent
import android.net.Uri
......@@ -10,16 +10,16 @@ import androidx.core.view.MenuItemCompat
import androidx.fragment.app.Fragment
import fr.chenry.android.freshrss.R
import fr.chenry.android.freshrss.store.Store
import fr.chenry.android.freshrss.store.dao.store.ContentItem
import fr.chenry.android.freshrss.store.dao.store.Article
import fr.chenry.android.freshrss.utils.capitalizeFull
import kotlinx.android.synthetic.main.fragment_subscription_content_detail.*
import kotlinx.android.synthetic.main.fragment_subscription_article_detail.*
import java.util.regex.Pattern
import kotlin.text.RegexOption.IGNORE_CASE
class SubscriptionContentDetailFragment: Fragment() {
class SubscriptionArticlesDetailFragment: Fragment() {
private lateinit var subscriptionContentsId: String
private lateinit var subscriptionArticleId: String
private lateinit var article: ContentItem
private lateinit var article: Article
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
......@@ -33,7 +33,7 @@ class SubscriptionContentDetailFragment: Fragment() {
article = Store.getArticle(subscriptionContentsId, subscriptionArticleId).get()
activity.let {it as AppCompatActivity}.supportActionBar?.apply {subtitle = article.title}
return inflater.inflate(R.layout.fragment_subscription_content_detail, container, false)
return inflater.inflate(R.layout.fragment_subscription_article_detail, container, false)
}
override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) {
......
package fr.chenry.android.freshrss.components.subscriptionarticles
import android.os.Bundle
import android.view.*
import androidx.fragment.app.Fragment
import androidx.lifecycle.*
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import fr.chenry.android.freshrss.R
import fr.chenry.android.freshrss.store.dao.store.Articles
import fr.chenry.android.freshrss.store.databindingsupport.viewmodels.*
import fr.chenry.android.freshrss.utils.*
sealed class SubscriptionArticlesFragment: Fragment() {
protected open lateinit var model: ArticlesViewModel
lateinit var streamId: String
protected set
val articles: Articles get() = model.articles
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
streamId = arguments?.getString("id")!!
model.streamId.value = streamId
return inflater.inflate(R.layout.fragment_subscription_articles, container, false)
.let {
it as RecyclerView
it.layoutManager = LinearLayoutManager(context)
it.adapter = RecyclerViewAdapter(this)
it
}
}
}
class SubscriptionAllArticlesFragment: SubscriptionArticlesFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activity?.run {
model = ViewModelProviders.of(this).get(ArticlesViewModel::class.java)
}
}
}
class SubscriptionUnreadArticlesFragment: SubscriptionArticlesFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activity?.run {
model = ViewModelProviders.of(this).get(UnreadArticlesViewModel::class.java)
}
}
}
package fr.chenry.android.freshrss.components.subscriptioncontents
import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import fr.chenry.android.freshrss.R
class SubscriptionContentsFragment: Fragment() {
private lateinit var subscriptionContentsId: String
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
subscriptionContentsId = arguments?.getString("id")!!
return inflater.inflate(R.layout.fragment_subscription_contents, container, false).apply {
with(this as RecyclerView) {
layoutManager = LinearLayoutManager(context)
adapter = SubscriptionContentsRecyclerViewAdapter(subscriptionContentsId)
}
}
}
}
package fr.chenry.android.freshrss.components.subscriptioncontents
import android.view.*
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import fr.chenry.android.freshrss.R
import fr.chenry.android.freshrss.components.subscriptions.SubscriptionSection
import fr.chenry.android.freshrss.store.*
import fr.chenry.android.freshrss.store.dao.store.ContentItems
import fr.chenry.android.freshrss.utils.getOrDefault
import kotlinx.android.synthetic.main.fragment_subscription_content.view.*
class SubscriptionContentsRecyclerViewAdapter(private val streamId: String):
RecyclerView.Adapter<SubscriptionContentsRecyclerViewAdapter.ViewHolder>()
{
private val contentItems: ContentItems = when(Store.subscriptionsSection.value) {
SubscriptionSection.ALL -> Store.getStream(streamId).getOrDefault(listOf())
SubscriptionSection.UNREAD -> Store.getUnreadItemsForStream(streamId)
else -> Store.getStream(streamId).getOrDefault(listOf())
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.fragment_subscription_content, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = contentItems[position]
holder.contentAuthor.text = item.author
holder.contentTitle.text = item.title
with(holder.mView) {
tag = item
id = position
// Handle errors when article or stream could not be found
setOnClickListener {
Router.navigate(RouteName.SUBSCRIPTION_CONTENT_DETAIL,
mapOf("streamId" to streamId, "articleId" to item.id))
}
}
}
override fun getItemCount(): Int = contentItems.size
inner class ViewHolder(val mView: View): RecyclerView.ViewHolder(mView) {
val contentAuthor: TextView = mView.subscription_content_author
val contentTitle: TextView = mView.subscription_content_title
override fun toString(): String {
return "${super.toString()}{contentAuthor: ${contentAuthor.text}, contentTitle: ${contentTitle.text}}"
}
}
}
package fr.chenry.android.freshrss.components.subscriptions
import fr.chenry.android.freshrss.store.Store
import fr.chenry.android.freshrss.store.*
import fr.chenry.android.freshrss.store.dao.common.Subscription
import fr.chenry.android.freshrss.store.dao.common.Subscriptions
class AllSubscriptionsFragment: SubscriptionsFragment() {
override val subscriptions: Subscriptions get() = Store.subscriptions.values
override fun onClick(subscription: Subscription) {
Router.navigate(RouteName.SUBSCRIPTION_ARTICLES_ALL, mapOf("id" to subscription.id))
}
}
......@@ -7,6 +7,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import fr.chenry.android.freshrss.R
import fr.chenry.android.freshrss.store.Store
import fr.chenry.android.freshrss.store.dao.common.Subscription
import fr.chenry.android.freshrss.store.dao.common.Subscriptions
import kotlinx.android.synthetic.main.fragment_main_subscription.*
import kotlin.reflect.KClass
......@@ -69,4 +70,6 @@ abstract class SubscriptionsFragment: Fragment() {
return view
}
abstract fun onClick(subscription: Subscription)
}
\ No newline at end of file
package fr.chenry.android.freshrss.components.subscriptions
import android.view.*
import androidx.annotation.UiThread
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.RecyclerView
import fr.chenry.android.freshrss.BR
......@@ -22,21 +21,19 @@ class RecyclerViewAdapter(private val fragment: SubscriptionsFragment):
return ViewHolder(binding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(fragment.subscriptions[position])
@UiThread
fun emit() = notifyDataSetChanged()
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val subscription = fragment.subscriptions[position]
holder.bind(subscription)
holder.binding.root.setOnClickListener {fragment.onClick(subscription)}
}
override fun getItemCount(): Int = fragment.subscriptions.size
inner class ViewHolder(private val binding: FragmentSubscriptionBinding):
inner class ViewHolder(val binding: FragmentSubscriptionBinding):
RecyclerView.ViewHolder(binding.root) {
fun bind(subscription: Subscription) {
binding.setVariable(BR.subscription, subscription)
binding.root.setOnClickListener {
Router.navigate(RouteName.SUBSCRIPTION_CONTENTS, mapOf("id" to subscription.id))
}
binding.executePendingBindings()
}
}
......
package fr.chenry.android.freshrss.components.subscriptions
import android.os.Bundle
import android.view.*
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import fr.chenry.android.freshrss.R
import fr.chenry.android.freshrss.store.RouteName
import fr.chenry.android.freshrss.store.Router
import fr.chenry.android.freshrss.store.dao.common.Subscription
import fr.chenry.android.freshrss.store.dao.common.Subscriptions
import fr.chenry.android.freshrss.store.databindingsupport.viewmodels.UnreadContentViewModel
import fr.chenry.android.freshrss.store.databindingsupport.viewmodels.UnreadSubscriptionsViewModel
class UnreadSubscriptionsFragment: SubscriptionsFragment() {
private lateinit var model: UnreadContentViewModel
private lateinit var model: UnreadSubscriptionsViewModel
override val subscriptions: Subscriptions get() = model.subscriptions
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activity?.run {
model = ViewModelProviders.of(this).get(UnreadContentViewModel::class.java)
model = ViewModelProviders.of(this).get(UnreadSubscriptionsViewModel::class.java)
}
}
override fun onClick(subscription: Subscription) {
Router.navigate(RouteName.SUBSCRIPTION_ARTICLES_UNREAD, mapOf("id" to subscription.id))
}
}
......@@ -3,10 +3,8 @@ package fr.chenry.android.freshrss.store
import androidx.annotation.MainThread
import androidx.fragment.app.Fragment
import androidx.lifecycle.MutableLiveData
import fr.chenry.android.freshrss.components.subscriptioncontents.SubscriptionContentDetailFragment
import fr.chenry.android.freshrss.components.subscriptioncontents.SubscriptionContentsFragment
import fr.chenry.android.freshrss.components.subscriptionarticles.*
import fr.chenry.android.freshrss.components.subscriptions.MainSubscriptionFragment
import fr.chenry.android.freshrss.components.subscriptions.UnreadSubscriptionsFragment
import fr.chenry.android.freshrss.store.Router.Route
import kotlin.reflect.KClass
......@@ -15,14 +13,15 @@ enum class RouteName {
SUBSCRIPTIONS_ALL,
SUBSCRIPTIONS_UNREAD,
SUBSCRIPTIONS_FAVORITES,
SUBSCRIPTION_CONTENTS,
SUBSCRIPTION_ARTICLES_ALL,
SUBSCRIPTION_ARTICLES_UNREAD,
SUBSCRIPTION_CONTENT_DETAIL
}
@MainThread
object Router {
private val currentRoute = MutableLiveData<Route>()
@MainThread
private fun navigate(to: Route?, params: Map<String, Any>, pushHistory: Boolean) {
if (to == null) return
......@@ -44,11 +43,9 @@ object Router {
currentRoute.value = newTo.withParameters(parameters.toMap())
}
@MainThread
fun navigate(to: RouteName, params: Map<String, Any> = mapOf(), pushHistory: Boolean = true) =
this.navigate(routes.find { it.name.name == to.name }, params, pushHistory)
@MainThread
fun observe(onRouteChange: (Route) -> Unit) = currentRoute.observeForever { onRouteChange(it) }
data class Route(
......@@ -66,6 +63,7 @@ object Router {
private val routes = listOf(
Route("/subscriptions", RouteName.SUBSCRIPTIONS, MainSubscriptionFragment::class),
Route("/subscription/:id", RouteName.SUBSCRIPTION_CONTENTS, SubscriptionContentsFragment::class),
Route("/subscription/:streamId/article/:articleId", RouteName.SUBSCRIPTION_CONTENT_DETAIL, SubscriptionContentDetailFragment::class)
Route("/subscription/:id/all", RouteName.SUBSCRIPTION_ARTICLES_ALL, SubscriptionAllArticlesFragment::class),
Route("/subscription/:id/unread", RouteName.SUBSCRIPTION_ARTICLES_UNREAD, SubscriptionUnreadArticlesFragment::class),
Route("/subscription/:streamId/article/:articleId", RouteName.SUBSCRIPTION_CONTENT_DETAIL, SubscriptionArticlesDetailFragment::class)
)
......@@ -3,22 +3,21 @@ package fr.chenry.android.freshrss.store
import androidx.lifecycle.MutableLiveData
import fr.chenry.android.freshrss.components.subscriptions.SubscriptionSection
import fr.chenry.android.freshrss.store.api.Api
import fr.chenry.android.freshrss.store.dao.common.StreamId
import fr.chenry.android.freshrss.store.dao.common.Subscription
import fr.chenry.android.freshrss.store.dao.store.ContentItem
import fr.chenry.android.freshrss.store.dao.store.ContentItems
import fr.chenry.android.freshrss.store.dao.store.*
import fr.chenry.android.freshrss.store.databindingsupport.GenericLiveData
import nl.komponents.kovenant.*
import nl.komponents.kovenant.Kovenant.deferred
import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.resolve
import nl.komponents.kovenant.ui.failUi
import nl.komponents.kovenant.ui.successUi
object Store {
var debugMode = false
val api = Api()
val subscriptions = GenericLiveData<String, Subscription>()
val subscriptions = GenericLiveData<StreamId, Subscription>()
val totalUnreadCount = MutableLiveData<Int>()
val contentItems = GenericLiveData<String, ContentItems>()
val contentItems = GenericLiveData<StreamId, GenericLiveData<ItemId, Article>>()
val subscriptionsSection = MutableLiveData<SubscriptionSection>().apply {this.value = SubscriptionSection.ALL}
val tags = MutableLiveData<List<String>>().apply {this.value = listOf()}
val favorites = GenericLiveData<String, Subscription>(mutableMapOf())
......@@ -60,23 +59,20 @@ object Store {
}
fun getStreamContents(id: String): Promise<Unit, Exception> {
val deferred = deferred<Unit, Exception>()
api.getStreamContents(id) successUi {
contentItems[id] = it.items.map {item ->
ContentItem(
item.id,
item.title,
item.alternate.first().href,
item.categories,
item.author,
item.summary.content
)
}
return api.getStreamContents(id) then {
contentItems[id] = GenericLiveData(
it.items.map {item ->
item.id to Article(
item.id,
item.title,
item.alternate.first().href,
item.categories,
item.author,
item.summary.content
)
}.toMap())
contentItems.emit()
deferred.resolve()
} failUi (deferred::reject)
return deferred.promise
}
}
fun getTags(): Promise<Unit, Exception> {
......@@ -88,9 +84,9 @@ object Store {
fun getUnreadItems(): Promise<Unit, Exception> {
val deferred = deferred<Unit, Exception>()
api.getUnreadItems(lastFetchTimestamp) successUi {
val unreadItems = it.items.groupBy({c -> c.origin.streamId}, {c -> c.id to c.timestampUsec})
unreadItems.forEach { u ->
subscriptions[u.key]?.unreadList = u.value.sortedBy {i -> i.second}.map {i -> i.first}
val unreadItems = it.items.groupBy {c -> c.origin.streamId}
unreadItems.forEach {u ->
subscriptions[u.key]?.unreadList = u.value.sortedBy {i -> i.timestampUsec}.map {i -> i.id}
}
lastFetchTimestamp = System.currentTimeMillis() % 1000
deferred.resolve()
......@@ -100,30 +96,11 @@ object Store {
return deferred.promise
}
fun getStream(streamId: String): Promise<ContentItems, StreamNotFoundException> {
val stream = contentItems[streamId]
return if(stream != null)
Promise.ofSuccess(stream) else
Promise.ofFail(StreamNotFoundException(streamId))
fun getArticle(streamId: String, articleId: String): Promise<Article, ArticleNotFoundException> {
val article = contentItems[streamId]?.get(articleId)
return if(article == null) Promise.ofFail(ArticleNotFoundException(articleId)) else Promise.ofSuccess(article)
}
fun getArticle(streamId: String, articleId: String): Promise<ContentItem, ArticleNotFoundException> {
return try {
val article = getStream(streamId).get().find {article -> article.id == articleId}!!
Promise.ofSuccess(article)
} catch(_: Throwable) {
Promise.ofFail(ArticleNotFoundException(articleId))
}
}
fun getUnreadItemsForStream(streamId: String): List<ContentItem> {
val subscription = subscriptions[streamId] ?: return listOf()
return subscription.unreadList
.map { articleId -> contentItems[streamId]?.find { it.id == articleId } }
.filter { it != null } as List<ContentItem>
}
private fun getToken(): Promise<Unit, Exception> {
val deferred = deferred<Unit, Exception>()
this.api.getToken() success {deferred.resolve(Unit)} fail (deferred::reject)
......@@ -131,5 +108,4 @@ object Store {
}
}
class StreamNotFoundException(val id: String): Exception("Stream with id $id cound not be found in store")
class ArticleNotFoundException(val id: String): Exception("Article with id $id cound not be found in store")
\ No newline at end of file
package fr.chenry.android.freshrss.store.api
import com.github.kittinunf.fuel.Fuel
import fr.chenry.android.freshrss.store.dao.AuthTokens
import fr.chenry.android.freshrss.store.dao.api.*
import fr.chenry.android.freshrss.store.dao.common.Subscriptions
import fr.chenry.android.freshrss.store.dao.common.SubscriptionsHandler
import fr.chenry.android.freshrss.utils.*
import com.github.kittinunf.fuel.Fuel
import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.functional.bind
import nl.komponents.kovenant.then
import nl.komponents.kovenant.ui.successUi
......@@ -37,7 +38,7 @@ class Api {
}
fun getToken(): Promise<String, Exception> {
if (!isLogged()) return Promise.ofFail(ApiNotInitializedException())
if(!isLogged()) return Promise.ofFail(ApiNotInitializedException())
return Fuel
.post(endpoints.tokenEndpoint)
......@@ -52,7 +53,7 @@ class Api {
.getJson(endpoints.subscriptionEndpoint)
.authorize(this.authTokens)
.promise(SubscriptionsHandler.serializer())
.then { it.subscriptions }
.then {it.subscriptions}
}
fun getUnreadCount(): Promise<UnreadCountsHandler, Exception> {
......@@ -70,7 +71,7 @@ class Api {
return Fuel
.getJson(endpoints.streamItemsEndpoint, listOf("s" to id))
.authorize(this.authTokens)
.promiseString() successUi { this.e(it) }
.promiseString() successUi {this.e(it)}
}
fun getStreamContents(id: String): Promise<ContentItemsHandler, Exception> {
......@@ -91,17 +92,28 @@ class Api {
.promiseWithSerializer(TagsDeserializer)
}
fun getUnreadItems(olderTimestamp: Long, excludStreamId: String? = null): Promise<ContentItemsHandler, Exception> {
fun getUnreadItems(
olderTimestamp: Long,
excludStreamId: String? = null,
continuation: String? = null
): Promise<ContentItemsHandler, Exception> {
if(!isLogged()) return Promise.ofFail(ApiNotInitializedException())
val params = if(excludStreamId.isNullOrBlank())
listOf("ot" to "$olderTimestamp") else
listOf("ot" to "$olderTimestamp", "xt" to excludStreamId)
val params = mutableListOf<Pair<String, String>>()
if(!continuation.isNullOrBlank()) params.add("c" to continuation)
if(!excludStreamId.isNullOrBlank()) params.add("xt" to excludStreamId)
if(olderTimestamp > 0) params.add("ot" to "$olderTimestamp")
return Fuel
.getJson(endpoints.unreadItemsEndpoint, params)
.authorize(this.authTokens)
.promise()
.authorize(authTokens)
.promise<ContentItemsHandler>()
.bind {it1 ->
if(it1.continuation.isNotBlank() && it1.continuation != continuation) {
getUnreadItems(olderTimestamp, excludStreamId, it1.continuation)
.then {it2 -> it2.copy(items = it1.items + it2.items)}
} else Promise.ofSuccess(it1)
}
}
private fun isLogged() = ::endpoints.isInitialized && ::authTokens.isInitialized
......
......@@ -19,11 +19,11 @@ class Endpoints(base: String) {
fun computeApiUrl(fromUrl: String): String {
return when(hasProtocol(fromUrl) to hasApiEndpoint(fromUrl)) {
(false to false) -> "$defaultProtocol$fromUrl$apiEndpoint"
(false to true) -> "$defaultProtocol$fromUrl"
(false to false) -> "$defaultProtocol${"$fromUrl$apiEndpoint".cleanUrlSlashes()}"
(false to true) -> "$defaultProtocol${fromUrl.cleanUrlSlashes()}"
(true to false) -> "$fromUrl$apiEndpoint"
else -> fromUrl
}.cleanUrlSlashes()
}
}
fun hasProtocol(url: String) = url.matches("^https?://.*$".toRegex())
......
......@@ -7,7 +7,9 @@ import kotlinx.serialization.Serializable
data class ContentItemsHandler(
val id: String,
val updated: Long,
val items: ContentItems
val items: ContentItems,
@Optional