Commit 3088922f authored by Christophe Henry's avatar Christophe Henry

Fix multiple display bugs:

- Articles not sorted by publication date
- Display a message stating all articles have been read after comming back from a feed with a single unread article
parent 7ef529c4
......@@ -84,9 +84,9 @@ dependencies {
implementation "com.android.support:support-core-ui:$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.appcompat:appcompat:1.1.0-alpha03"
implementation "androidx.core:core-ktx:1.1.0-alpha05"
implementation "com.google.android.material:material:1.1.0-alpha04"
implementation "androidx.constraintlayout:constraintlayout:1.1.3"
implementation "androidx.recyclerview:recyclerview:1.0.0"
......
package fr.chenry.android.freshrss.components.subscriptionarticles
import android.view.*
import android.view.LayoutInflater
import android.view.ViewGroup
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.database.models.Article
import fr.chenry.android.freshrss.store.database.models.Articles
......@@ -14,6 +14,7 @@ class RecyclerViewAdapter(private val fragment: SubscriptionArticlesFragment):
RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder>()
{
var articles: Articles = listOf()
val subscription get() = fragment.model.subscription
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
......@@ -35,6 +36,7 @@ class RecyclerViewAdapter(private val fragment: SubscriptionArticlesFragment):
inner class ViewHolder(val binding: FragmentSubscriptionArticleBinding): RecyclerView.ViewHolder(binding.root) {
fun bind(article: Article) {
binding.setVariable(BR.article, article)
binding.setVariable(BR.subscription, subscription)
binding.executePendingBindings()
}
}
......
......@@ -15,8 +15,8 @@ import fr.chenry.android.freshrss.store.Store
import fr.chenry.android.freshrss.store.database.models.Article
import fr.chenry.android.freshrss.store.database.models.ReadStatus
import fr.chenry.android.freshrss.store.database.models.ReadStatus.UNREAD
import fr.chenry.android.freshrss.store.viewmodels.SubscriptionArticlesVM
import fr.chenry.android.freshrss.store.viewmodels.SubscriptionArticlesVMF
import fr.chenry.android.freshrss.store.viewmodels.SubscriptionArticleVM
import fr.chenry.android.freshrss.store.viewmodels.SubscriptionArticleVMF
import fr.chenry.android.freshrss.utils.capitalizeFull
import fr.chenry.android.freshrss.utils.e
import kotlinx.android.synthetic.main.fragment_subscription_article_detail.*
......@@ -27,10 +27,10 @@ import kotlin.text.RegexOption.IGNORE_CASE
class SubscriptionArticlesDetailFragment: Fragment() {
private lateinit var articleId: String
private val model: SubscriptionArticlesVM by lazy {
private val model: SubscriptionArticleVM by lazy {
ViewModelProviders
.of(this, SubscriptionArticlesVMF(articleId))
.get(SubscriptionArticlesVM::class.java)
.of(this, SubscriptionArticleVMF(articleId))
.get(SubscriptionArticleVM::class.java)
}
private var isFetching = MutableLiveData<Boolean>().apply {value = false}
private val article: Article get() = model.liveData.value!!
......
......@@ -3,41 +3,41 @@ package fr.chenry.android.freshrss.components.subscriptionarticles
import android.os.Bundle
import android.view.*
import androidx.fragment.app.Fragment
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import fr.chenry.android.freshrss.R
import fr.chenry.android.freshrss.store.database.FreshRSSDabatabase
import fr.chenry.android.freshrss.store.Store
import fr.chenry.android.freshrss.store.database.models.Articles
import fr.chenry.android.freshrss.store.database.models.ReadStatus
import fr.chenry.android.freshrss.store.database.models.ReadStatus.READ
import fr.chenry.android.freshrss.store.database.models.ReadStatus.UNREAD
import fr.chenry.android.freshrss.store.viewmodels.SubscriptionArticlesVM
import fr.chenry.android.freshrss.store.viewmodels.SubscriptionArticlesVMF
import kotlinx.android.synthetic.main.fragment_subscription_articles.*
open class SubscriptionArticlesFragment: Fragment(), Observer<Articles> {
private val args: SubscriptionArticlesFragmentArgs by navArgs()
private val streamId: String by lazy {args.id}
private val readStatus: ReadStatus by lazy {args.readStatus}
private val args: SubscriptionArticlesFragmentArgs by navArgs()
private val model: LiveData<Articles> by lazy {
when(readStatus) {
READ -> FreshRSSDabatabase.instance.getArticleByStreamId(streamId)
UNREAD -> FreshRSSDabatabase.instance.getArticleByStreamIdAndUnread(streamId)
}
val model: SubscriptionArticlesVM by lazy {
ViewModelProviders
.of(this, SubscriptionArticlesVMF(streamId, readStatus))
.get(SubscriptionArticlesVM::class.java)
}
private val adapter by lazy {
RecyclerViewAdapter(this).apply {
model.observe(this@SubscriptionArticlesFragment, this@SubscriptionArticlesFragment)
model.liveData.observe(this@SubscriptionArticlesFragment, this@SubscriptionArticlesFragment)
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_subscription_articles, container, false)
model.observe(this, this)
model.liveData.observe(this, this)
view.findViewById<RecyclerView>(R.id.fragment_subscription_article_recycler).apply {
layoutManager = LinearLayoutManager(context)
......@@ -49,8 +49,7 @@ open class SubscriptionArticlesFragment: Fragment(), Observer<Articles> {
override fun onChanged(articles: Articles?) {
(articles ?: listOf()).let {
fragment_subscription_article_waiting.visibility = if(it.isNotEmpty()) View.GONE else View.VISIBLE
fragment_subscription_article_recycler.visibility = if(it.isEmpty()) View.GONE else View.VISIBLE
toggleProgressCircle(it)
adapter.articles = it
adapter.notifyDataSetChanged()
}
......@@ -62,4 +61,21 @@ open class SubscriptionArticlesFragment: Fragment(), Observer<Articles> {
.actionSubscriptionArticlesFragmentToSubscriptionArticlesDetailFragment(articleId)
view?.findNavController()?.navigate(direction)
}
private fun toggleProgressCircle(articles: Articles) {
fragment_subscription_article_empty_list.text = when(readStatus) {
ReadStatus.READ -> context?.getString(R.string.empty_subscription_list, model.subscription.title)
ReadStatus.UNREAD -> context?.getString(R.string.empty_subscription_unread_list, model.subscription.title)
}
if(Store.refreshingPromise.value == null) {
fragment_subscription_article_recycler.visibility = if(articles.isNotEmpty()) View.VISIBLE else View.GONE
fragment_subscription_article_empty_list.visibility = if(articles.isEmpty()) View.VISIBLE else View.GONE
fragment_subscription_article_waiting.visibility = View.GONE
} else {
fragment_subscription_article_recycler.visibility = if(articles.isNotEmpty()) View.VISIBLE else View.GONE
fragment_subscription_article_waiting.visibility = if(articles.isEmpty()) View.VISIBLE else View.GONE
fragment_subscription_article_empty_list.visibility = View.GONE
}
}
}
......@@ -18,7 +18,6 @@ import fr.chenry.android.freshrss.store.viewmodels.*
import kotlinx.android.synthetic.main.fragment_subscriptions.*
class SubscriptionsFragment: Fragment(), Observer<Subscriptions> {
val subscriptions get() = model.liveData.value ?: listOf()
private val subscriptionSection by lazy {
arguments?.getParcelable(argumentKey) ?: SubscriptionSection.ALL
}
......@@ -36,7 +35,7 @@ class SubscriptionsFragment: Fragment(), Observer<Subscriptions> {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_subscriptions, container, false)
Store.refreshingPromise.observe(this, Observer {toggleProgressCircle()})
Store.refreshingPromise.observe(this, Observer {toggleProgressCircle(model.liveData.value ?: listOf())})
model.liveData.observe(this, this)
view.findViewById<RecyclerView>(R.id.subscriptions_list).let {
......@@ -45,13 +44,13 @@ class SubscriptionsFragment: Fragment(), Observer<Subscriptions> {
}
when(subscriptionSection) {
ALL -> FreshRSSApplication.getStringR(R.string.empty_subscription_list_all)
UNREAD -> FreshRSSApplication.getStringR(R.string.empty_subscription_list_unread)
FAVORITES -> FreshRSSApplication.getStringR(R.string.empty_subscription_list_favorites)
ALL -> FreshRSSApplication.getStringR(R.string.empty_subscriptions_all_category)
UNREAD -> FreshRSSApplication.getStringR(R.string.empty_subscriptions_unread_category)
FAVORITES -> FreshRSSApplication.getStringR(R.string.empty_subscriptions_favorites_categeory)
}.let {
view
.findViewById<TextView>(R.id.fragment_subscriptions_empty_list)
.text = FreshRSSApplication.getStringR(R.string.empty_subscription_list, it)
.text = FreshRSSApplication.getStringR(R.string.empty_subscriptions_category_list, it)
}
return view
......@@ -59,7 +58,7 @@ class SubscriptionsFragment: Fragment(), Observer<Subscriptions> {
override fun onChanged(subscriptions: Subscriptions?) {
(subscriptions ?: listOf()).let {
toggleProgressCircle()
toggleProgressCircle(it)
adapter.notifyDataSetChanged()
}
}
......@@ -72,7 +71,7 @@ class SubscriptionsFragment: Fragment(), Observer<Subscriptions> {
}.let {view?.findNavController()?.navigate(it)}
}
private fun toggleProgressCircle() {
private fun toggleProgressCircle(subscriptions: Subscriptions) {
if(Store.refreshingPromise.value == null) {
subscriptions_list.visibility = if(subscriptions.isNotEmpty()) View.VISIBLE else View.GONE
fragment_subscriptions_empty_list.visibility = if(subscriptions.isEmpty()) View.VISIBLE else View.GONE
......
......@@ -14,7 +14,7 @@ import nl.komponents.kovenant.functional.bind
import org.joda.time.LocalDateTime
import kotlin.reflect.KProperty
@Database(version = 4, entities = [Account::class, Article::class, Subscription::class])
@Database(version = 5, entities = [Account::class, Article::class, Subscription::class])
@TypeConverters(Converters::class)
@GenerateRoomMigrations
abstract class FreshRSSDabatabase: RoomDatabase() {
......@@ -67,6 +67,7 @@ abstract class FreshRSSDabatabase: RoomDatabase() {
fun incrementSubscriptionCount(id: String) = getSubscriptionsDAO().incrementCount(id)
fun decrementSubscriptionCount(id: String) = getSubscriptionsDAO().decrementCount(id)
fun insertSubscriptionImage(id: String, bitmap: Bitmap) = getSubscriptionsDAO().insertImage(id, bitmap)
fun getSubcriptionsById(id: String) = getSubscriptionsDAO().byId(id)
fun getAllSubcriptions() = getSubscriptionsDAO().getAll()
fun getAllUnreadSubcriptions() = getSubscriptionsDAO().getAllUnread()
fun getAllSubcriptionsIds() = getSubscriptionsDAO().getAllIds()
......
{
"formatVersion": 1,
"database": {
"version": 5,
"identityHash": "b2015cd8bc68ba0a77910e0c7e793bc2",
"entities": [
{
"tableName": "accounts",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `SID` TEXT NOT NULL, `Auth` TEXT NOT NULL, `login` TEXT NOT NULL, `serverInstance` TEXT NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "SID",
"columnName": "SID",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "Auth",
"columnName": "Auth",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "login",
"columnName": "login",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "serverInstance",
"columnName": "serverInstance",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "articles",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `title` TEXT NOT NULL, `href` TEXT NOT NULL, `categories` TEXT NOT NULL, `author` TEXT NOT NULL, `content` TEXT NOT NULL, `streamId` TEXT NOT NULL, `readStatus` TEXT NOT NULL, `crawled` INTEGER NOT NULL, `published` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "href",
"columnName": "href",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "categories",
"columnName": "categories",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "author",
"columnName": "author",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "streamId",
"columnName": "streamId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "readStatus",
"columnName": "readStatus",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "crawled",
"columnName": "crawled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "published",
"columnName": "published",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "subscriptions",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`imageBitmap` BLOB, `id` TEXT NOT NULL, `title` TEXT NOT NULL, `iconUrl` TEXT NOT NULL, `unreadCount` INTEGER NOT NULL, `newestArticleDate` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "imageBitmap",
"columnName": "imageBitmap",
"affinity": "BLOB",
"notNull": false
},
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "iconUrl",
"columnName": "iconUrl",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "unreadCount",
"columnName": "unreadCount",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "newestArticleDate",
"columnName": "newestArticleDate",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"b2015cd8bc68ba0a77910e0c7e793bc2\")"
]
}
}
\ No newline at end of file
......@@ -3,7 +3,6 @@ package fr.chenry.android.freshrss.store.database.models
import android.net.Uri
import android.os.Parcelable
import androidx.databinding.BaseObservable
import androidx.databinding.Bindable
import androidx.lifecycle.LiveData
import androidx.room.*
import fr.chenry.android.freshrss.store.api.models.ContentItem
......@@ -27,7 +26,8 @@ data class Article(
val content: String,
val streamId: StreamId,
val readStatus: ReadStatus = ReadStatus.READ,
val crawled: LocalDateTime = LocalDateTime(0)
val crawled: LocalDateTime = LocalDateTime(0),
val published: LocalDateTime = LocalDateTime(0)
): BaseObservable() {
@Ignore
val url = Try{Uri.parse(href)}.getOrNull()
......@@ -41,7 +41,8 @@ data class Article(
item.author,
item.summary.content,
item.origin.streamId,
crawled = item.crawled
crawled = item.crawled,
published = item.published
)
}
}
......
......@@ -76,7 +76,7 @@ interface SubscriptionsDAO {
fun getAllUnread(): Flowable<Subscriptions>
@Query("SELECT * FROM subscriptions WHERE id = :id")
fun byId(id: String): Flowable<Subscription>
fun byId(id: String): Flowable<Subscriptions>
@Query("SELECT * FROM subscriptions WHERE LENGTH(imageBitmap) IS NULL")
fun withImageToUpdate(): Flowable<Subscriptions>
......
package fr.chenry.android.freshrss.store.viewmodels
import androidx.lifecycle.*
import fr.chenry.android.freshrss.store.database.FreshRSSDabatabase
import fr.chenry.android.freshrss.store.database.models.*
class SubscriptionArticleVM(private val articleId: ItemId): ViewModel() {
private val flowable by lazy { FreshRSSDabatabase.instance.getArticleById(articleId) }
private val source: LiveData<Articles> by lazy { flowable.toLiveData() }
val liveData: LiveData<Article> by lazy {
MutableLiveData<Article>().apply {
value = flowable.blockingFirst().first()
source.observeForever { value = it.first() }
}
}
}
class SubscriptionArticleVMF(private val articleId: ItemId): ViewModelProvider.NewInstanceFactory() {
override fun <T: ViewModel?> create(modelClass: Class<T>): T {
@Suppress("UNCHECKED_CAST")
return SubscriptionArticleVM(articleId) as T
}
}
\ No newline at end of file
package fr.chenry.android.freshrss.store.viewmodels
import androidx.lifecycle.*
import fr.chenry.android.freshrss.store.database.FreshRSSDabatabase
import fr.chenry.android.freshrss.FreshRSSApplication
import fr.chenry.android.freshrss.store.database.models.*
class SubscriptionArticlesVM(private val articleId: ItemId): ViewModel() {
private val flowable by lazy { FreshRSSDabatabase.instance.getArticleById(articleId) }
private val source: LiveData<Articles> by lazy { flowable.toLiveData() }
val liveData: LiveData<Article> by lazy {
MutableLiveData<Article>().apply {
value = flowable.blockingFirst().first()
source.observeForever { value = it.first() }
class SubscriptionArticlesVM(streamId: String, readStatus: ReadStatus): ViewModel() {
val liveData: LiveData<Articles>
var subscription: Subscription
private set
private val subscriptionLiveData: LiveData<Subscriptions>
private val source: LiveData<Articles>
init {
val flowable = FreshRSSApplication.database.getSubcriptionsById(streamId)
subscription = flowable.blockingFirst().first()
subscriptionLiveData = flowable.toLiveData()
subscriptionLiveData.observeForever{if(it.isNotEmpty()) subscription = it.first()}
source = when(readStatus) {
ReadStatus.READ -> FreshRSSApplication.database.getArticleByStreamId(streamId)
ReadStatus.UNREAD -> FreshRSSApplication.database.getArticleByStreamIdAndUnread(streamId)
}
liveData = MutableLiveData<Articles>().apply {
value = listOf()
source.observeForever {value = it.sortedByDescending {article -> article.published}}
}
}
}
class SubscriptionArticlesVMF(private val articleId: ItemId): ViewModelProvider.NewInstanceFactory() {
class SubscriptionArticlesVMF(private val streamId: String, private val readStatus: ReadStatus): ViewModelProvider.NewInstanceFactory() {
override fun <T: ViewModel?> create(modelClass: Class<T>): T {
@Suppress("UNCHECKED_CAST")
return SubscriptionArticlesVM(articleId) as T
return SubscriptionArticlesVM(streamId, readStatus) as T
}
}
\ No newline at end of file
......@@ -2,12 +2,8 @@ package fr.chenry.android.freshrss.store.viewmodels
import androidx.lifecycle.*
import fr.chenry.android.freshrss.FreshRSSApplication
import fr.chenry.android.freshrss.store.database.models.Subscriptions
import fr.chenry.android.freshrss.R
import fr.chenry.android.freshrss.store.Store
import fr.chenry.android.freshrss.utils.e
import fr.chenry.android.freshrss.utils.whenNull
import nl.komponents.kovenant.task
import fr.chenry.android.freshrss.store.database.models.Subscriptions
import org.joda.time.LocalDateTime
sealed class SubscriptionsVM: ViewModel() {
......
......@@ -3,21 +3,27 @@
xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="article" type="fr.chenry.android.freshrss.store.database.models.Article"/>
<variable name="subscription" type="fr.chenry.android.freshrss.store.database.models.Subscription"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="@dimen/text_margin">
android:paddingStart="@dimen/subscription_section_h_padding"
android:paddingEnd="@dimen/subscription_section_h_padding"
android:paddingTop="@dimen/text_margin"
android:paddingBottom="@dimen/text_margin">
<TextView
android:text="@{article.title, default=`Title`}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceListItem" />
android:textAppearance="?attr/textAppearanceListItem"
android:maxLines="1"
android:ellipsize="end" />
<TextView
android:text="@{article.author, default=`Author`}"
android:text="@{subscription.title, default=`Author`}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceListItem"
......
......@@ -30,4 +30,15 @@
class="fr.chenry.android.freshrss.components.waiting.WaitingFragment"
android:layout_margin="0dp" />
</LinearLayout>
<TextView
android:id="@+id/fragment_subscription_article_empty_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/empty_subscription_list"
android:visibility="gone"
android:gravity="center|center_horizontal|center_vertical"
android:layout_gravity="center"
android:textColor="@color/light_grey"
android:textSize="24sp"
android:textStyle="bold" />
</LinearLayout>
\ No newline at end of file
......@@ -28,7 +28,7 @@
android:id="@+id/fragment_subscriptions_empty_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/empty_subscription_list"
android:text="@string/empty_subscriptions_category_list"
android:visibility="gone"
android:gravity="center|center_horizontal|center_vertical"
android:layout_gravity="center"
......
......@@ -2,7 +2,7 @@
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="text_margin">16dp</dimen>
<dimen name="text_margin">10dp</dimen>
<dimen name="icon_size">20dp</dimen>
<dimen name="subscription_section_v_padding">4dp</dimen>
<dimen name="subscription_section_h_padding">8dp</dimen>
......
......@@ -41,10 +41,12 @@
<string name="request_already_ongoing">A request is already ongoing. Please retry later.</string>
<string name="unable_to">Sorry, the application failed to %s</string>
<string name="empty_subscription_list">You have no %s yet</string>
<string name="empty_subscription_list_favorites">favorites</string>
<string name="empty_subscription_list_all">subscriptions</string>
<string name="empty_subscription_list_unread">unread articles</string>
<string name="empty_subscriptions_category_list">You have no %s yet</string>
<string name="empty_subscriptions_favorites_categeory">favorites</string>
<string name="empty_subscriptions_all_category">subscriptions</string>
<string name="empty_subscriptions_unread_category">unread articles</string>
<string name="empty_subscription_list">There is no articles on %s feed yes</string>
<string name="empty_subscription_unread_list">You have read every article on %s feed!</string>
<string name="human_time_grouping_today">Today</string>
<string name="human_time_grouping_yesterday">Yesterday</string>
......
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