Commit d7ee5478 authored by Christophe Henry's avatar Christophe Henry
Browse files

Merge branch 'remove-app-singleton' into 'develop'

Remove Android application singleton

See merge request !120
parents 3ec99a43 81212693
Pipeline #4861 failed with stage
in 0 seconds
......@@ -148,6 +148,7 @@ dependencies {
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-reactivestreams-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
kapt "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
// Navigation
......
......@@ -19,7 +19,7 @@ class FreshRSSTestRunner: AndroidJUnitRunner() {
val article = ArticleFactory.createArticle(streamId = feed.id)
override val db: FreshRSSDabatabase by lazy {
Room.databaseBuilder(context, FreshRSSDabatabase::class.java, TEST_DB_NAME)
Room.databaseBuilder(this, FreshRSSDabatabase::class.java, TEST_DB_NAME)
.fallbackToDestructiveMigration()
.build().apply {
runBlocking {
......
......@@ -5,25 +5,27 @@ import android.app.NotificationChannel
import android.content.Context
import android.content.SharedPreferences
import android.os.Build
import androidx.annotation.StringRes
import androidx.annotation.VisibleForTesting
import androidx.core.app.NotificationManagerCompat
import androidx.lifecycle.*
import androidx.room.Room
import androidx.work.*
import fr.chenry.android.freshrss.store.FreshRSSPreferences
import fr.chenry.android.freshrss.store.Store
import fr.chenry.android.freshrss.store.database.FreshRSSDabatabase
import fr.chenry.android.freshrss.store.database.FreshRSSDabatabase_Migrations
import fr.chenry.android.freshrss.utils.*
import kotlin.math.max
import kotlinx.coroutines.*
import java.util.Properties
import java.util.concurrent.TimeUnit
import org.acra.ACRA
import org.acra.annotation.*
import org.acra.data.StringFormat
import org.acra.sender.HttpSender
import org.joda.time.*
import java.util.Properties
import java.util.TimeZone
import java.util.concurrent.TimeUnit
import kotlin.math.max
import org.joda.time.DateTime
import org.joda.time.Period
import fr.chenry.android.freshrss.store.FreshRSSPreferences
import fr.chenry.android.freshrss.store.Store
import fr.chenry.android.freshrss.store.database.FreshRSSDabatabase
import fr.chenry.android.freshrss.store.database.FreshRSSDabatabase_Migrations
import fr.chenry.android.freshrss.utils.*
@AcraCore(reportFormat = StringFormat.JSON)
@AcraHttpSender(
......@@ -39,11 +41,12 @@ import kotlin.math.max
resChannelImportance = NotificationManagerCompat.IMPORTANCE_MAX
)
open class FreshRSSApplication: Application(), SharedPreferences.OnSharedPreferenceChangeListener {
open val preferences: FreshRSSPreferences by lazy {FreshRSSPreferences(this)}
open val workManager by lazy {WorkManager.getInstance(this)}
open val notificationManager: NotificationManagerCompat by lazy {NotificationManagerCompat.from(this)}
open val db by lazy {
Room.databaseBuilder(context, FreshRSSDabatabase::class.java, DB_NAME)
Room.databaseBuilder(this, FreshRSSDabatabase::class.java, DB_NAME)
.addMigrations(*FreshRSSDabatabase_Migrations.build())
.build()
}
......@@ -58,7 +61,9 @@ open class FreshRSSApplication: Application(), SharedPreferences.OnSharedPrefere
// Stupid hack because Android is still not able to provide the current
// application globally even though it's an actual a singleton...
appInstance = this
notificationManager.createNotificationChannels(getNotificationChannels())
ProcessLifecycleOwner.get().lifecycle.addObserver(NotificationCreator())
preferences.apply {
initDefaults()
registerChangeListener(this@FreshRSSApplication)
......@@ -138,7 +143,6 @@ open class FreshRSSApplication: Application(), SharedPreferences.OnSharedPrefere
).await()
companion object {
const val DB_NAME = "freshrss.db"
private lateinit var appInstance: FreshRSSApplication
val app: FreshRSSApplication get() = appInstance
......@@ -149,26 +153,25 @@ open class FreshRSSApplication: Application(), SharedPreferences.OnSharedPrefere
val context: Context
inline get() = app.applicationContext
val preferences: FreshRSSPreferences
inline get() = app.preferences
val userTimeZone: DateTimeZone
inline get() = DateTimeZone.forTimeZone(TimeZone.getDefault())
fun getString(id: Int, vararg formatArgs: Any = arrayOf()): String = if(formatArgs.isEmpty())
app.resources.getString(id) else
app.resources.getString(id, *formatArgs)
}
private fun getNotificationChannels(): List<NotificationChannel> {
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return listOf()
private inner class NotificationCreator: LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun createChannels() = notificationManager.createNotificationChannels(getNotificationChannels())
return NotificationChannels.values().map {
private fun getNotificationChannels(): List<NotificationChannel> =
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.O) listOf()
else NotificationChannels.values().map {
NotificationChannel(it.name, getString(it.nameResourceId), it.importance.asAndroid()).apply {
description = getString(it.descriptionResourceId)
setSound(null, null)
}
}
}
private fun getString(@StringRes resId: Int): String = this@FreshRSSApplication.getString(resId)
}
}
......
......@@ -120,12 +120,12 @@ class LoginActivity: AppCompatActivity() {
val protocole = activity_login_protocol_selection.selectedItem.toString()
instanceUrl = InstanceUrl(protocole, instanceStr)
reified_instance_url.text = instanceUrl.toSpanString()
reified_instance_url.text = instanceUrl.toSpanString(this)
}
private fun handleError(err: Throwable) {
showProgress(false)
this.e(err)
this.e(err.toString())
instance.error = getString(R.string.error_instance)
if(err is ServerException) {
when(err.response.code) {
......
......@@ -10,7 +10,6 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.navArgs
import fr.chenry.android.freshrss.R
import fr.chenry.android.freshrss.components.articles.webviewutils.FRSSWebView
......@@ -20,7 +19,7 @@ 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.viewmodels.ArticleVM
import fr.chenry.android.freshrss.store.viewmodels.ArticleVMF
import fr.chenry.android.freshrss.store.viewmodels.providers.ArticleViewModelProvider
import fr.chenry.android.freshrss.utils.*
import kotlinx.android.synthetic.main.fragment_subscription_article_detail.*
import kotlinx.coroutines.*
......@@ -30,9 +29,9 @@ class ArticleDetailFragment: Fragment(), View.OnClickListener {
private val articleId by lazy {args.articleId}
private val model: ArticleVM by lazy {
ViewModelProvider(requireActivity().viewModelStore, ArticleVMF(requireF(), articleId))
.get("articleId=$articleId", ArticleVM::class.java)
ArticleViewModelProvider(requireActivity(), articleId).get(ArticleVM::class.java)
}
private val article: Article inline get() = model.liveData.value!!
private val feedTitle: String inline get() = model.feedTitle.value!!
private val supportActionBar inline get() = (requireActivity() as? AppCompatActivity)?.supportActionBar
......
......@@ -21,6 +21,7 @@ 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.Subscription
import fr.chenry.android.freshrss.store.viewmodels.*
import fr.chenry.android.freshrss.store.viewmodels.providers.ArticlesViewModelProvider
import fr.chenry.android.freshrss.utils.*
import kotlinx.android.synthetic.main.fragment_subscription_articles.*
import kotlinx.coroutines.*
......@@ -32,8 +33,7 @@ class ArticlesFragment: Fragment() {
private val workInfos by viewModels<RefreshWorkInfosVM>()
private val model: ArticlesVM by lazy {
ViewModelProvider(requireActivity().viewModelStore, ArticlesVMF(requireF(), subscription, section))
.get("stream=${subscription.id}:section=$section", ArticlesVM::class.java)
ArticlesViewModelProvider(requireActivity(), subscription, section).get(ArticlesVM::class.java)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
......
......@@ -7,14 +7,14 @@ import android.os.Bundle
import android.widget.*
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import fr.chenry.android.freshrss.F
import fr.chenry.android.freshrss.R
import fr.chenry.android.freshrss.utils.*
import java.net.URL
class AddSubscriptionDialog(private val callback: (String) -> Unit): DialogFragment() {
private val clipboard by lazy {
F.context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
}
private val dialogView by lazy {
......@@ -53,6 +53,7 @@ class AddSubscriptionDialog(private val callback: (String) -> Unit): DialogFragm
}
companion object {
/**
* Will try to detect a Youtube's user profile or playlist URL and return the correct
* Youtube's RSS feed for the user or playlist.
......
......@@ -5,6 +5,8 @@ import android.os.Bundle
import android.view.*
import androidx.databinding.ObservableBoolean
import androidx.fragment.app.*
import androidx.lifecycle.Transformations
import androidx.lifecycle.liveData
import androidx.viewpager.widget.ViewPager
import com.google.android.material.bottomnavigation.BottomNavigationView
import fr.chenry.android.freshrss.*
......@@ -33,7 +35,9 @@ class MainSubscriptionFragment: Fragment(), SharedPreferences.OnSharedPreference
FragmentMainSubscriptionBinding.bind(view).apply {
lifecycleOwner = this@MainSubscriptionFragment.viewLifecycleOwner
viewModel = this@MainSubscriptionFragment.viewModel
lastFetchText = Transformations.map(viewModel.lastFetchText) {
it.resolve(requireContext())
}
autoFetchDisabled = this@MainSubscriptionFragment.autoFetchDisabled
}
......@@ -43,7 +47,7 @@ class MainSubscriptionFragment: Fragment(), SharedPreferences.OnSharedPreference
viewPager.adapter = MainSubscriptionPagerAdapter()
viewPager.offscreenPageLimit = SubscriptionSection.values().size
F.app.preferences.subscriptionSection.let {
requireF().preferences.subscriptionSection.let {
bottomNav.selectedItemId = it.navigationButtonId
viewPager.setCurrentItem(it.ordinal, false)
subscriptionSectionVM.section.value = it
......@@ -64,7 +68,7 @@ class MainSubscriptionFragment: Fragment(), SharedPreferences.OnSharedPreference
override fun onPause() {
super.onPause()
F.preferences.subscriptionSection = subscriptionSectionVM.section.value!!
requireF().preferences.subscriptionSection = subscriptionSectionVM.section.value!!
}
override fun onDestroy() {
......
......@@ -5,7 +5,6 @@ import android.view.*
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager
......@@ -16,22 +15,22 @@ import fr.chenry.android.freshrss.components.subscriptions.SubscriptionSection.*
import fr.chenry.android.freshrss.components.subscriptions.adapters.FeedLikeViewItems
import fr.chenry.android.freshrss.components.subscriptions.adapters.SubscriptionsFlexibleAdapter
import fr.chenry.android.freshrss.store.database.models.*
import fr.chenry.android.freshrss.store.viewmodels.*
import fr.chenry.android.freshrss.store.viewmodels.RefreshWorkInfosVM
import fr.chenry.android.freshrss.store.viewmodels.SubscriptionsVM
import fr.chenry.android.freshrss.store.viewmodels.providers.SubscriptionsViewModelProvider
import fr.chenry.android.freshrss.utils.EmotionnalImageSubtext
import fr.chenry.android.freshrss.utils.requireF
import kotlinx.android.synthetic.main.fragment_subscriptions.*
class SubscriptionsFragment: Fragment(), Observer<FeedLikeViewItems> {
private val args: SubscriptionsFragmentArgs by navArgs()
private val section by lazy {args.section}
private val category by lazy {args.category}
private val workInfos by activityViewModels<RefreshWorkInfosVM>()
val model by lazy {
val key = "section=${section.name}${if(category != VoidCategory) ":category=${category.id}" else ""}"
ViewModelProvider(requireActivity().viewModelStore, SubscriptionsFragmentCategoryVMF(category, section))
.get(key, SubscriptionsVM::class.java)
SubscriptionsViewModelProvider(requireActivity(), category, section).get(SubscriptionsVM::class.java)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
......
......@@ -11,8 +11,9 @@ import fr.chenry.android.freshrss.F
import fr.chenry.android.freshrss.R
import java.util.concurrent.ConcurrentHashMap
open class SubscriptionViewHeaderItem protected constructor(val title: String) :
AbstractHeaderItem<SubscriptionViewHeaderItem.SubscriptionViewHeaderHolder>() {
open class SubscriptionViewHeaderItem protected constructor(
val title: String
): AbstractHeaderItem<SubscriptionViewHeaderItem.Holder>() {
init {
isDraggable = false
isSelectable = false
......@@ -21,26 +22,26 @@ open class SubscriptionViewHeaderItem protected constructor(val title: String) :
override fun bindViewHolder(
adapter: FlexibleAdapter<IFlexible<ViewHolder>>?,
holder: SubscriptionViewHeaderHolder,
holder: Holder,
position: Int,
payloads: MutableList<Any>?
) = holder.bind(title)
override fun createViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<ViewHolder>>?):
SubscriptionViewHeaderHolder =
SubscriptionViewHeaderHolder(view!!, adapter)
override fun createViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<ViewHolder>>?) =
Holder(view!!, adapter)
override fun getLayoutRes(): Int = R.layout.fragment_subscription_header
override fun equals(other: Any?): Boolean =
if (other !is SubscriptionViewHeaderItem) false else title == other.title
if(other !is SubscriptionViewHeaderItem) false else title == other.title
override fun hashCode(): Int = title.hashCode()
inner class SubscriptionViewHeaderHolder(
inner class Holder(
val view: View,
adapter: FlexibleAdapter<IFlexible<ViewHolder>>?
) : FlexibleViewHolder(view, adapter, true) {
): FlexibleViewHolder(view, adapter, true) {
fun bind(header: String) {
view.findViewById<TextView>(R.id.subscription_section_header_title).text = header
}
......@@ -49,8 +50,8 @@ open class SubscriptionViewHeaderItem protected constructor(val title: String) :
companion object {
private val headers = ConcurrentHashMap<String, SubscriptionViewHeaderItem>()
fun of(title: String): SubscriptionViewHeaderItem =
headers.getOrPut(title) { SubscriptionViewHeaderItem(title) }
headers.getOrPut(title) {SubscriptionViewHeaderItem(title)}
}
}
object SubscriptionCategoryViewHeaderItem : SubscriptionViewHeaderItem(F.getString(R.string.subscription_categories))
object SubscriptionCategoryViewHeaderItem: SubscriptionViewHeaderItem(F.getString(R.string.subscription_categories))
......@@ -3,11 +3,8 @@ package fr.chenry.android.freshrss.store.database
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import androidx.room.TypeConverter
import fr.chenry.android.freshrss.F
import fr.chenry.android.freshrss.store.database.models.FavoriteStatus
import fr.chenry.android.freshrss.store.database.models.FavoriteStatus.NOT_FAVORITE
import fr.chenry.android.freshrss.store.database.models.ReadStatus
import fr.chenry.android.freshrss.store.database.models.ReadStatus.READ
import fr.chenry.android.freshrss.utils.*
import org.joda.time.DateTime
import org.joda.time.LocalDateTime
......@@ -15,10 +12,12 @@ import org.joda.time.format.ISODateTimeFormat
import java.io.ByteArrayOutputStream
interface DbSerializable {
val dbName: String
}
class Converters {
@TypeConverter
fun readStatusFromString(value: String?) = ReadStatus.dbValueOf(value)
......@@ -46,7 +45,7 @@ class Converters {
if(byteArray?.isEmpty() != false) null else BitmapFactory.decodeStream(byteArray.inputStream())
@TypeConverter
fun localDateTimeToLong(localDateTime: LocalDateTime) = localDateTime.toDateTime(F.userTimeZone).millis
fun localDateTimeToLong(localDateTime: LocalDateTime) = localDateTime.toDateTime(getUserTimeZone()).millis
@TypeConverter
fun longToLocalDateTime(epoch: Long) = runCatching {LocalDateTime(epoch)}
......@@ -57,7 +56,8 @@ class Converters {
fun dateTimeToString(dateTime: DateTime): String = dateTime.toString(ISODateTimeFormat.basicDateTime())
@TypeConverter
fun stringToDateTime(string: String): DateTime = runCatching {DateTime.parse(string, ISODateTimeFormat.basicDateTime())}
.onFailure(this::e)
.getOrDefault(DateTime(0))
fun stringToDateTime(string: String): DateTime =
runCatching {DateTime.parse(string, ISODateTimeFormat.basicDateTime())}
.onFailure(this::e)
.getOrDefault(DateTime(0))
}
......@@ -26,29 +26,33 @@ class AccountVM: ViewModel() {
val unreadCount: Int inline get() = account.value?.unreadCount ?: 0
val lastFetchText: LiveData<String?> by lazy {
val lastFetchText: LiveData<ResourceResolver> by lazy {
Flowable.combineLatest(listOf(ticker, source)) {(_, it) ->
runBlocking {
withContext(Dispatchers.IO) {
it as Account
if(it.lastFetchDate.millis == 0L) return@withContext ""
if(it.lastFetchDate.millis == 0L) return@withContext emptyStringResourceResolver()
val sevenDaysAgo = DateTime.now(getUserTimeZone()).minusDays(7)
val userLocale = getUserLocale(F.context)
if(it.lastFetchDate.isAfter(sevenDaysAgo)) {
val periodFormat = PeriodFormat.wordBased()
val period = it.lastFetchDate.period(DateTime.now())
if(period.toStandardDuration().isShorterThan(Duration(20_000)))
F.getString(R.string.last_sync_a_few_seconds_ago)
idResResolver(R.string.last_sync_a_few_seconds_ago)
else
F.getString(R.string.last_sync_relative_date_text, periodFormat.print(period))
idResResolver(R.string.last_sync_relative_date_text) {
+functionResourceResolver {
PeriodFormat.wordBased(userLocale).print(period)
}
}
} else {
F.getString(
R.string.last_sync_absolute_date_text,
it.lastFetchDate.toString(DateTimeFormat.patternForStyle("M-", userLocale))
)
idResResolver(R.string.last_sync_absolute_date_text) {
+functionResourceResolver {
it.lastFetchDate.toString(DateTimeFormat.patternForStyle("M-", userLocale))
}
}
}
}
}
......
......@@ -7,6 +7,7 @@ import fr.chenry.android.freshrss.utils.requireF
import io.reactivex.Flowable
class ArticleVM(application: Application, articleId: String): AndroidViewModel(application) {
val liveData: LiveData<Article> = liveData {
val flowable: Flowable<Article> = requireF().db.getArticleById(articleId)
emit(flowable.blockingFirst())
......@@ -21,15 +22,3 @@ class ArticleVM(application: Application, articleId: String): AndroidViewModel(a
}
}
}
class ArticleVMF(
private val application: Application,
private val articleId: String
): ViewModelProvider.NewInstanceFactory() {
override fun <T: ViewModel?> create(modelClass: Class<T>): T {
@Suppress("UNCHECKED_CAST")
return ArticleVM(application, articleId) as T
}
}
package fr.chenry.android.freshrss.store.viewmodels
import android.app.Application
import androidx.lifecycle.*
import fr.chenry.android.freshrss.FreshRSSApplication
import fr.chenry.android.freshrss.components.articles.ArticleViewItem
import fr.chenry.android.freshrss.components.subscriptions.SubscriptionSection
import fr.chenry.android.freshrss.store.database.models.*
import fr.chenry.android.freshrss.utils.requireF
import io.reactivex.Flowable
......@@ -11,7 +10,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.concurrent.TimeUnit
sealed class ArticlesVM(app: FreshRSSApplication, val subscription: Subscription): AndroidViewModel(app) {
sealed class ArticlesVM(app: Application, val subscription: Subscription): AndroidViewModel(app) {
protected abstract val source: Flowable<Articles>
val liveData: LiveData<List<ArticleViewItem>> = liveData {
......@@ -32,29 +31,14 @@ sealed class ArticlesVM(app: FreshRSSApplication, val subscription: Subscription
private fun empty(): Articles = emptyList()
}
class AllArticlesVM(app: FreshRSSApplication, subscription: Subscription): ArticlesVM(app, subscription) {
class AllArticlesVM(app: Application, subscription: Subscription): ArticlesVM(app, subscription) {
override val source: Flowable<Articles> = requireF().db.getArticlesByStreamId(subscription.id)
}
class UnreadArticlesVM(app: FreshRSSApplication, subscription: Subscription): ArticlesVM(app, subscription) {
class UnreadArticlesVM(app: Application, subscription: Subscription): ArticlesVM(app, subscription) {
override val source: Flowable<Articles> = requireF().db.getArticleByStreamIdAndUnread(subscription.id)
}
class FavoritesArticlesVM(app: FreshRSSApplication, subscription: Subscription): ArticlesVM(app, subscription) {
class FavoritesArticlesVM(app: Application, subscription: Subscription): ArticlesVM(app, subscription) {
override val source: Flowable<Articles> = requireF().db.getArticleByStreamIdAndFavorite(subscription.id)
}
class ArticlesVMF(
private val application: FreshRSSApplication,
private val subscription: Subscription,
private val section: SubscriptionSection
): ViewModelProvider.NewInstanceFactory() {
@Suppress("UNCHECKED_CAST")
override fun <T: ViewModel?> create(modelClass: Class<T>): T = when(section) {
SubscriptionSection.ALL -> AllArticlesVM(application, subscription)
SubscriptionSection.UNREAD -> UnreadArticlesVM(application, subscription)
SubscriptionSection.FAVORITES -> FavoritesArticlesVM(application, subscription)
} as T
}
package fr.chenry.android.freshrss.store.viewmodels
import android.annotation.SuppressLint
import fr.chenry.android.freshrss.F
import fr.chenry.android.freshrss.R
import fr.chenry.android.freshrss.store.database.models.*
import org.joda.time.LocalDateTime
import java.util.Locale
object LabelComparator: Comparator<FeedTag> {
override fun compare(o1: FeedTag, o2: FeedTag): Int =
......@@ -35,9 +35,8 @@ interface Grouper {
}
object TitleGrouper: Grouper {
@SuppressLint("DefaultLocale")
override operator fun invoke(subscription: Subscription): String =
subscription.title.getOrElse(0) {'#'}.toString().toUpperCase()
subscription.title.getOrElse(0) {'#'}.toString().toUpperCase(Locale.getDefault())
}
object DateGrouper: Grouper {
......
package fr.chenry.android.freshrss.store.viewmodels
import android.app.Application
import androidx.lifecycle.*
import fr.chenry.android.freshrss.F
import fr.chenry.android.freshrss.components.subscriptions.SubscriptionSection
import fr.chenry.android.freshrss.components.subscriptions.SubscriptionSection.*
import fr.chenry.android.freshrss.components.subscriptions.adapters.*
import fr.chenry.android.freshrss.components.subscriptions.adapters.FeedLikeViewItem
import fr.chenry.android.freshrss.components.subscriptions.adapters.FeedLikeViewItems
import fr.chenry.android.freshrss.store.database.models.*
import fr.chenry.android.freshrss.utils.requireF
import io.reactivex.Flowable
import kotlinx.coroutines.*
import java.util.concurrent.TimeUnit
sealed class SubscriptionsVM: ViewModel() {
sealed class SubscriptionsVM(application: Application): AndroidViewModel(application) {
val subscriptions: LiveData<FeedLikeViewItems> = liveData(block = ::liveDataBlock)
protected abstract val grouper: Grouper
......@@ -25,12 +28,12 @@ sealed class SubscriptionsVM: ViewModel() {
val firstResult = withContext(Dispatchers.IO) {
val subscriptionsAsync = async {
rawSubscriptions
.blockingFirst(listOf<Subscription>())
.blockingFirst(listOf())