Commit 63925e70 authored by Christophe Henry's avatar Christophe Henry

Merge branch '31' into 'develop'

Solves #31: browse feeds by category

See merge request !10
parents e1977b7b 02e63744
......@@ -8,7 +8,8 @@
* Add animation in views's transtions ([4f84e6b5](https://git.feneas.org/christophehenry/freshrss-android/commit/4f84e6b5f6de7d42a24d541e099a402d034792da))
* Implement [#10](https://git.feneas.org/christophehenry/freshrss-android/issues/10) : fetch subscription's icons and display them ([!4](https://git.feneas.org/christophehenry/freshrss-android/merge_requests/4))
* Add sections and section headers to subscriptions ([!10](https://git.feneas.org/christophehenry/freshrss-android/merge_requests/5))
* Implement [#45](https://git.feneas.org/christophehenry/freshrss-android/issues/45): add badge to unread articles section to indicate total count of unread articles ([!9](https://git.feneas.org/christophehenry/freshrss-android/merge_requests/9))
* Implement [#45](https://git.feneas.org/christophehenry/freshrss-android/issues/45): add badge to unread articles section to indicate total count of unread articles ([!9](https://git.feneas.org/christophehenry/freshrss-android/merge_requests/9))
* Implement [#31](https://git.feneas.org/christophehenry/freshrss-android/issues/31): browse feeds by category ([!10](https://git.feneas.org/christophehenry/freshrss-android/merge_requests/10))
## Bug fixes
......
......@@ -11,9 +11,9 @@ import androidx.lifecycle.Observer
import androidx.viewpager.widget.ViewPager.SimpleOnPageChangeListener
import com.google.android.material.bottomnavigation.BottomNavigationItemView
import com.google.android.material.bottomnavigation.BottomNavigationView
import fr.chenry.android.freshrss.FreshRSSApplication
import fr.chenry.android.freshrss.R
import fr.chenry.android.freshrss.*
import fr.chenry.android.freshrss.store.Store
import fr.chenry.android.freshrss.store.database.models.VoidCategory
import fr.chenry.android.freshrss.utils.whenNotNull
import kotlinx.android.synthetic.main.fragment_main_subscription.*
import kotlinx.android.synthetic.main.menu_badge.*
......@@ -82,28 +82,30 @@ class MainSubscriptionFragment: Fragment(), BottomNavigationView.OnNavigationIte
}
private fun setupBadgeStyle(activeSubscriptionSection: SubscriptionSection) {
if(activeSubscriptionSection == SubscriptionSection.UNREAD) {
val offset =
(resources.getDimensionPixelSize(R.dimen.badge_active_text_size) -
resources.getDimensionPixelSize(R.dimen.badge_inactive_text_size)) / 2
val offset =
(resources.getDimensionPixelSize(R.dimen.badge_active_text_size) -
resources.getDimensionPixelSize(R.dimen.badge_inactive_text_size))
if(activeSubscriptionSection == SubscriptionSection.UNREAD) {
menu_counter_badge_count.updateLayoutParams<LinearLayout.LayoutParams> {
setMargins(0, 0, 0, resources.getDimensionPixelSize(R.dimen.badge_margin_bottom) - offset)
marginStart = resources.getDimensionPixelSize(R.dimen.badge_margin_start) + offset
setMargins(0, 0, 0, resources.getDimensionPixelSize(R.dimen.badge_margin_bottom))
marginStart = resources.getDimensionPixelSize(R.dimen.badge_margin_start) + (offset / 2)
}
menu_counter_badge_count
.setTextSize(TypedValue.COMPLEX_UNIT_PX, resources.getDimension(R.dimen.badge_active_text_size))
menu_counter_badge_count.background = resources.getDrawable(R.drawable.red_circle_background)
menu_counter_badge_count.background =
resources.getDrawable(R.drawable.red_circle_background, activity!!.theme)
} else {
menu_counter_badge_count.updateLayoutParams<LinearLayout.LayoutParams> {
setMargins(0, 0, 0, resources.getDimensionPixelSize(R.dimen.badge_margin_bottom))
setMargins(0, 0, 0, resources.getDimensionPixelSize(R.dimen.badge_margin_bottom) - (offset * 2))
marginStart = resources.getDimensionPixelSize(R.dimen.badge_margin_start)
}
menu_counter_badge_count
.setTextSize(TypedValue.COMPLEX_UNIT_PX, resources.getDimension(R.dimen.badge_inactive_text_size))
menu_counter_badge_count.background = resources.getDrawable(R.drawable.grey_circle_background)
menu_counter_badge_count.background =
resources.getDrawable(R.drawable.grey_circle_background, activity!!.theme)
}
}
......@@ -111,11 +113,13 @@ class MainSubscriptionFragment: Fragment(), BottomNavigationView.OnNavigationIte
override fun getItem(position: Int) = instantiateFragment(SubscriptionSection.byPosition(position))
override fun getCount() = SubscriptionSection.values().size
private fun instantiateFragment(subscriptionSection: SubscriptionSection) =
Fragment.instantiate(
activity!!,
SubscriptionsFragment::class.qualifiedName!!,
Bundle().apply {putParcelable(SubscriptionsFragment.argumentKey, subscriptionSection)}
)
private fun instantiateFragment(subscriptionSection: SubscriptionSection): Fragment {
val arguments = MainNavDirections
.actionGlobalSubscriptionsFragment(VoidCategory, subscriptionSection)
.arguments
return childFragmentManager
.fragmentFactory
.instantiate(activity!!.classLoader, SubscriptionsFragment::class.qualifiedName!!, arguments)
}
}
}
......@@ -4,20 +4,58 @@ import android.view.View
import android.widget.TextView
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.RecyclerView
import fr.chenry.android.freshrss.BR
import androidx.recyclerview.widget.*
import fr.chenry.android.freshrss.*
import fr.chenry.android.freshrss.R
import fr.chenry.android.freshrss.databinding.FragmentSubscriptionBinding
import fr.chenry.android.freshrss.store.database.models.Subscription
import fr.chenry.android.freshrss.store.database.models.Subscriptions
import fr.chenry.android.freshrss.store.database.models.*
import io.github.luizgrp.sectionedrecyclerviewadapter.*
class RecyclerViewAdapter(private val fragment: SubscriptionsFragment): SectionedRecyclerViewAdapter() {
private val categorySectionTag = Char.MIN_VALUE.toString()
init {
addSection(categorySectionTag, RecyclerViewAdapterSection2(listOf()))
fragment.model.sectionnedLiveData.observe(fragment, Observer {
this.removeAllSections()
it.entries.forEach {self -> this.addSection(self.key, RecyclerViewAdapterSection(self.toPair()))}
this.notifyDataSetChanged()
it.entries.forEach {self ->
if(getSection(self.key) == null) addSection(self.key, RecyclerViewAdapterSection(self.key, self.value))
else {
val oldItems: Subscriptions = getSection(self.key)
?.let {s ->
s as RecyclerViewAdapterSection
val result = s.subscriptions
s.subscriptions = it[self.key] ?: listOf()
result
} ?: listOf()
val newItems: Subscriptions = it[self.key] ?: oldItems
DiffUtil
.calculateDiff(SectionDiffUtilsCallback(oldItems, newItems))
.dispatchUpdatesTo(SectionUpdateCallback(self.key))
}
}
})
fragment.model.subscriptionCategoriesLiveData.observe(fragment, Observer {
val oldItems = getSection(categorySectionTag)
?.let {s ->
s as RecyclerViewAdapterSection2
val result = s.subscriptionCategories
s.subscriptionCategories = it
result
} ?: listOf()
if(getSection(categorySectionTag).hasHeader() != it.isNotEmpty()) {
getSection(categorySectionTag).setHasHeader(it.isNotEmpty())
if(it.isNotEmpty()) notifyHeaderInsertedInSection(categorySectionTag)
else notifyHeaderRemovedFromSection(categorySectionTag)
}
DiffUtil
.calculateDiff(SectionDiffUtilsCallback(oldItems, it))
.dispatchUpdatesTo(SectionUpdateCallback(categorySectionTag))
})
}
......@@ -28,10 +66,10 @@ class RecyclerViewAdapter(private val fragment: SubscriptionsFragment): Sectione
}
}
inner class RecyclerViewAdapterSection(private val entry: Pair<String, Subscriptions>):
inner class RecyclerViewAdapterSection(private val sectionTag: String, var subscriptions: Subscriptions):
StatelessSection(getSectionParameters()) {
override fun onBindItemViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val subscription = entry.second[position]
val subscription = subscriptions[position]
(holder as ViewHolder).apply {
bind(subscription)
binding.root.setOnClickListener {fragment.onClick(subscription)}
......@@ -39,12 +77,56 @@ class RecyclerViewAdapter(private val fragment: SubscriptionsFragment): Sectione
}
override fun onBindHeaderViewHolder(holder: RecyclerView.ViewHolder) {
holder.itemView.findViewById<TextView>(R.id.subscription_section_title).text = entry.first
holder.itemView.findViewById<TextView>(R.id.subscription_section_header_title).text = sectionTag
}
override fun getItemViewHolder(view: View) = ViewHolder(DataBindingUtil.bind(view)!!)
override fun getContentItemsTotal() = subscriptions.size
}
inner class RecyclerViewAdapterSection2(var subscriptionCategories: SubscriptionCategories):
StatelessSection(getSectionParameters()) {
override fun onBindItemViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val subscriptionCategory = subscriptionCategories[position]
(holder as ViewHolder).apply {
bind(Subscription("", subscriptionCategory.label, "", -1, listOf()))
binding.root.setOnClickListener {fragment.onClick(subscriptionCategory)}
}
}
override fun onBindHeaderViewHolder(holder: RecyclerView.ViewHolder) {
holder.itemView.findViewById<TextView>(R.id.subscription_section_header_title).text =
FreshRSSApplication.getStringR(R.string.subscription_categories)
}
override fun getItemViewHolder(view: View) = ViewHolder(DataBindingUtil.bind(view)!!)
override fun getContentItemsTotal() = subscriptionCategories.size
}
inner class SectionUpdateCallback(private val sectionTag: String): ListUpdateCallback {
override fun onChanged(position: Int, count: Int, payload: Any?) =
notifyItemChangedInSection(sectionTag, position)
override fun onMoved(fromPosition: Int, toPosition: Int) =
notifyItemMovedInSection(sectionTag, fromPosition, toPosition)
override fun onInserted(position: Int, count: Int) = notifyItemInsertedInSection(sectionTag, position)
override fun onRemoved(position: Int, count: Int) = notifyItemRemovedFromSection(sectionTag, position)
}
inner class SectionDiffUtilsCallback<T>(private val oldItems: List<T>, private val newItems: List<T>):
DiffUtil.Callback() {
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldItems.getOrNull(oldItemPosition)
val newitem = newItems.getOrNull(newItemPosition)
return oldItem != null && newitem != null && oldItem.hashCode() == newitem.hashCode()
}
override fun getContentItemsTotal() = entry.second.size
override fun getOldListSize() = oldItems.size
override fun getNewListSize() = newItems.size
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
oldItems.getOrNull(oldItemPosition)?.equals(newItems.getOrNull(newItemPosition)) ?: false
}
companion object {
......
......@@ -6,7 +6,8 @@ import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.navigation.findNavController
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import fr.chenry.android.freshrss.*
......@@ -18,12 +19,16 @@ import fr.chenry.android.freshrss.store.viewmodels.*
import kotlinx.android.synthetic.main.fragment_subscriptions.*
class SubscriptionsFragment: Fragment(), Observer<Subscriptions> {
private val subscriptionSection by lazy {
arguments?.getParcelable(argumentKey) ?: SubscriptionSection.ALL
}
private val args: SubscriptionsFragmentArgs by navArgs()
private val section by lazy {args.section}
private val category by lazy {args.category}
val model by lazy {
when(subscriptionSection) {
if(category != VoidCategory)
ViewModelProviders
.of(this, SubscriptionsFragmentCategoryVMF(category, section))
.get(AllSubscriptionsVM::class.java)
else when(section) {
ALL -> ViewModelProviders.of(this).get(AllSubscriptionsVM::class.java)
UNREAD -> ViewModelProviders.of(this).get(UnreadSubscriptionsVM::class.java)
FAVORITES -> ViewModelProviders.of(this).get(FavoritesSubscriptionsVM::class.java)
......@@ -43,15 +48,16 @@ class SubscriptionsFragment: Fragment(), Observer<Subscriptions> {
it.adapter = this.adapter
}
when(subscriptionSection) {
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_subscriptions_category_list, it)
}
val contentDescription = if(category != VoidCategory)
FreshRSSApplication.getStringR(R.string.empty_subscriptions_category, category.label) else
when(section) {
ALL -> FreshRSSApplication.getStringR(R.string.empty_subscriptions_section_all)
UNREAD -> FreshRSSApplication.getStringR(R.string.empty_subscriptions_section_unread)
FAVORITES -> FreshRSSApplication.getStringR(R.string.empty_subscriptions_section_favorites)
}
view.findViewById<TextView>(R.id.fragment_subscriptions_empty_list).text =
FreshRSSApplication.getStringR(R.string.empty_subscription_section_template, contentDescription)
return view
}
......@@ -63,13 +69,15 @@ class SubscriptionsFragment: Fragment(), Observer<Subscriptions> {
}
}
fun onClick(subscription: Subscription) {
when(subscriptionSection) {
FAVORITES -> MainNavDirections.actionGlobalSubscriptionArticlesFragment(subscription.id, READ)
ALL -> MainNavDirections.actionGlobalSubscriptionArticlesFragment(subscription.id, READ)
UNREAD -> MainNavDirections.actionGlobalSubscriptionArticlesFragment(subscription.id, ReadStatus.UNREAD)
}.let {view?.findNavController()?.navigate(it)}
}
fun onClick(subscription: Subscription) = when(section) {
FAVORITES -> MainNavDirections.actionGlobalSubscriptionArticlesFragment(subscription.id, READ)
ALL -> MainNavDirections.actionGlobalSubscriptionArticlesFragment(subscription.id, READ)
UNREAD -> MainNavDirections.actionGlobalSubscriptionArticlesFragment(subscription.id, ReadStatus.UNREAD)
}.let {findNavController().navigate(it)}
fun onClick(subscriptionCategory: SubscriptionCategory) =
MainNavDirections.actionGlobalSubscriptionsFragment(subscriptionCategory, section)
.let {findNavController().navigate(it)}
private fun toggleProgressCircle(subscriptions: Subscriptions) {
if(Store.refreshingPromise.value == null) {
......@@ -82,8 +90,4 @@ class SubscriptionsFragment: Fragment(), Observer<Subscriptions> {
fragment_subscriptions_empty_list.visibility = View.GONE
}
}
companion object {
const val argumentKey = "subscriptionSection"
}
}
\ No newline at end of file
}
......@@ -7,6 +7,7 @@ import fr.chenry.android.freshrss.store.api.Api
import fr.chenry.android.freshrss.store.database.models.*
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.database.models.SubscriptionCategory.Companion.fromApiItem
import nl.komponents.kovenant.*
import nl.komponents.kovenant.functional.bind
import kotlin.math.max
......@@ -44,7 +45,7 @@ object Store {
fun getSubscriptions(): Promise<Unit, Exception> = api.getSubscriptions() bind {
val subscriptions = it.map {self -> Subscription.fromSubscriptionApiItem(self)}
FreshRSSApplication.database.syncSubscriptions(subscriptions).always {
val syncPromise = FreshRSSApplication.database.syncSubscriptions(subscriptions).always {
task {
FreshRSSApplication.database.let {db ->
db.getAllSubcriptionsWithImageToUpdate()
......@@ -52,7 +53,12 @@ object Store {
.forEach {sub -> db.insertSubscriptionImage(sub.id, sub.fetchImage())}
}
}
}.toSuccessVoid()
}
val categoriesPromise = task {
val subscriptionCategories = it.flatMap {self -> self.categories}.map(::fromApiItem)
FreshRSSApplication.database.insertAllSubscriptionCategories(subscriptionCategories)
}
all(syncPromise, categoriesPromise, cancelOthersOnError = false).toSuccessVoid()
}
fun getUnreadCount(): Promise<Unit, Exception> =
......
......@@ -14,13 +14,13 @@ data class SubscriptionApiItem(
val id: String,
@JsonDeserialize(using = HtmlEntitiesDeserializer::class)
val title: String,
val categories: List<SubscriptionCategory>,
val categories: List<SubscriptionCategoryApiItem> = listOf(),
val url: String,
val htmlUrl: String,
val iconUrl: String
)
data class SubscriptionCategory(
data class SubscriptionCategoryApiItem(
val id: String,
val label: String
)
......
......@@ -6,21 +6,25 @@ import dev.matrix.roomigrant.GenerateRoomMigrations
import fr.chenry.android.freshrss.store.api.models.StreamId
import fr.chenry.android.freshrss.store.database.models.*
import fr.chenry.android.freshrss.utils.Try
import fr.chenry.android.freshrss.utils.escapeHtml4
import nl.komponents.kovenant.*
import nl.komponents.kovenant.functional.bind
import org.joda.time.LocalDateTime
@Database(version = 5, entities = [Account::class, Article::class, Subscription::class])
@Database(version = 6, entities = [Account::class, Article::class, Subscription::class, SubscriptionCategory::class])
@TypeConverters(Converters::class)
@GenerateRoomMigrations
abstract class FreshRSSDabatabase: RoomDatabase() {
protected abstract fun getAuthTokensDAO(): AuthTokensDAO
protected abstract fun getArticlesDAO(): ArticlesDAO
protected abstract fun getSubscriptionsDAO(): SubscriptionsDAO
protected abstract fun getSubscriptionCategoriesDAO(): SubscriptionCategoriesDAO
// Accounts
fun getAccount() = getAuthTokensDAO().getAuthTokens()
fun insertAccount(account: Account) = getAuthTokensDAO().insert(account)
// Articles
fun getArticlesByStreamId(streamId: StreamId) = getArticlesDAO().getByStreamId(streamId)
fun getArticleById(id: ItemId) = getArticlesDAO().getById(id)
fun getArticleByStreamIdAndUnread(streamId: StreamId) =
......@@ -31,6 +35,7 @@ abstract class FreshRSSDabatabase: RoomDatabase() {
fun insertArticle(article: Article) = getArticlesDAO().forceInsert(article)
// Subscriptions
fun syncSubscriptions(subscriptions: Subscriptions) = task {
val inDatabase = getSubscriptionsDAO().getAll().blockingFirst().map {it.id to it}.toMap()
val inQuery = subscriptions.map {it.id to it}.toMap()
......@@ -44,7 +49,7 @@ abstract class FreshRSSDabatabase: RoomDatabase() {
inQuery.values
.filter {s1 -> !s1.areSimilar(inDatabase[s1.id])}
.forEach {value ->
getSubscriptionsDAO().updateValues(value.id, value.title, value.iconUrl)
getSubscriptionsDAO().updateValues(value.id, value.title, value.iconUrl, value.subscriptionCategories)
if(inDatabase[value.id]?.iconUrl != value.iconUrl) getSubscriptionsDAO().deleteImage(value.id)
}
}
......@@ -64,8 +69,18 @@ abstract class FreshRSSDabatabase: RoomDatabase() {
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 getSubcriptionsBySubscriptionCategory(subscriptionCategory: SubscriptionCategory) =
getSubscriptionsDAO().bySubscriptionCategory("%${subscriptionCategory.id.escapeHtml4()}%")
fun getSubcriptionsBySubscriptionCategoryAndUnreadCount(subscriptionCategory: SubscriptionCategory) =
getSubscriptionsDAO().bySubscriptionCategoryAndUnreadCount("%${subscriptionCategory.id.escapeHtml4()}%")
fun getAllSubcriptions() = getSubscriptionsDAO().getAll()
fun getAllUnreadSubcriptions() = getSubscriptionsDAO().getAllUnread()
fun getAllSubcriptionsIds() = getSubscriptionsDAO().getAllIds()
fun getAllSubcriptionsWithImageToUpdate() = getSubscriptionsDAO().withImageToUpdate()
// Subscription catagoeries
fun insertAllSubscriptionCategories(subscriptionCategories: List<SubscriptionCategory>) =
getSubscriptionCategoriesDAO().insertAll(subscriptionCategories)
fun getCategoriesById(ids: List<String>) = getSubscriptionCategoriesDAO().byIds(ids)
fun getAllCategories() = getSubscriptionCategoriesDAO().all()
}
\ No newline at end of file
{
"formatVersion": 1,
"database": {
"version": 6,
"identityHash": "48919e50c8f0abc005d0c05f3c42dfe2",
"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, `subscriptionCategories` TEXT 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": "subscriptionCategories",
"columnName": "subscriptionCategories",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "newestArticleDate",
"columnName": "newestArticleDate",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "subscription_categories",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `label` TEXT NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "label",
"columnName": "label",
"affinity": "TEXT",
"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, \"48919e50c8f0abc005d0c05f3c42dfe2\")"
]
}