Commit 7c671d6b authored by Christophe Henry's avatar Christophe Henry

Solves #43: ability to quickly mark as read/unread/favorite articles without having to open them

parent b4e87adb
......@@ -24,7 +24,7 @@ android {
}
lintOptions {
disable "AllowBackup", "VectorPath"
disable "AllowBackup", "VectorPath", "GradleDependency"
}
sourceSets {
......
package fr.chenry.android.freshrss.components.subscriptionarticles
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.database.models.Article
import fr.chenry.android.freshrss.store.database.models.Articles
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)
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 = articles[position]
holder.bind(article)
holder.binding.root.setOnClickListener { fragment.navigateToArticle(article.id) }
}
override fun getItemCount(): Int = articles.size
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()
}
}
}
package fr.chenry.android.freshrss.components.subscriptionarticles
import android.view.View
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.RecyclerView
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder
import fr.chenry.android.freshrss.BR
import fr.chenry.android.freshrss.R
import fr.chenry.android.freshrss.components.subscriptionarticles.SubscriptionArticleViewItem.ArticleViewHolder
import fr.chenry.android.freshrss.databinding.FragmentSubscriptionArticleBinding
import fr.chenry.android.freshrss.store.database.models.Article
import fr.chenry.android.freshrss.store.database.models.Subscription
class SubscriptionArticleViewItem(val article: Article, private val subscription: Subscription) :
AbstractFlexibleItem<ArticleViewHolder>() {
init {
isDraggable = false
isSelectable = false
isSwipeable = true
}
override fun bindViewHolder(
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>,
holder: ArticleViewHolder,
position: Int,
payloads: MutableList<Any>
) = holder.bind(article)
override fun equals(other: Any?): Boolean {
if (other !is SubscriptionArticleViewItem) return false
return article == other.article
}
override fun createViewHolder(
view: View,
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>
): ArticleViewHolder = ArticleViewHolder(DataBindingUtil.bind(view)!!, adapter)
override fun getLayoutRes(): Int = R.layout.fragment_subscription_article
override fun hashCode(): Int = article.hashCode()
inner class ArticleViewHolder(
private val binding: FragmentSubscriptionArticleBinding,
flexibleAdapter: FlexibleAdapter<*>
) : FlexibleViewHolder(binding.root, flexibleAdapter) {
private val frontView = binding.root.findViewById<View>(R.id.fragment_subscription_article_front_view)
private val rearLeftView =
binding.root.findViewById<View>(R.id.fragment_subscription_article_rear_left_view)
fun bind(article: Article) {
binding.setVariable(BR.article, article)
binding.setVariable(BR.subscription, subscription)
binding.executePendingBindings()
}
override fun getFrontView(): View = frontView
override fun getRearLeftView(): View = rearLeftView
override fun getActivationElevation(): Float = 1f
}
}
......@@ -9,38 +9,43 @@ import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.FlexibleAdapter.OnItemClickListener
import fr.chenry.android.freshrss.R
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.*
import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.ui.alwaysUi
open class SubscriptionArticlesFragment : Fragment(), Observer<Articles> {
class SubscriptionArticlesFragment : Fragment(), Observer<List<SubscriptionArticleViewItem>> {
private val args: SubscriptionArticlesFragmentArgs by navArgs()
private val streamId: String by lazy { args.id }
private val readStatus: ReadStatus by lazy { args.readStatus }
val model: SubscriptionArticlesVM by lazy {
private val model: SubscriptionArticlesVM by lazy {
ViewModelProviders
.of(this, SubscriptionArticlesVMF(streamId, readStatus))
.get(SubscriptionArticlesVM::class.java)
.get(SubscriptionArticlesVM::class.java).apply {
liveData.observe(this@SubscriptionArticlesFragment, this@SubscriptionArticlesFragment)
}
}
private val adapter by lazy {
RecyclerViewAdapter(this).apply {
model.liveData.observe(this@SubscriptionArticlesFragment, this@SubscriptionArticlesFragment)
}
ArticleAdapter(model.liveData.value ?: listOf())
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
val view = inflater.inflate(R.layout.fragment_subscription_articles, container, false)
model.liveData.observe(this, this)
view.findViewById<RecyclerView>(R.id.fragment_subscription_article_recycler).apply {
layoutManager = LinearLayoutManager(context)
adapter = this@SubscriptionArticlesFragment.adapter
......@@ -49,25 +54,29 @@ open class SubscriptionArticlesFragment : Fragment(), Observer<Articles> {
return view
}
override fun onChanged(articles: Articles?) {
(articles ?: listOf()).let {
override fun onChanged(articlesViewItemSubscriptions: List<SubscriptionArticleViewItem>?) {
(articlesViewItemSubscriptions ?: listOf()).let {
toggleProgressCircle(it)
adapter.articles = it
adapter.notifyDataSetChanged()
adapter.updateDataSet(it, true)
}
}
fun navigateToArticle(articleId: String) {
val direction =
SubscriptionArticlesFragmentDirections
fun onItemClick(position: Int): Boolean =
adapter.getItem(position)?.article?.id?.let { articleId ->
val direction = SubscriptionArticlesFragmentDirections
.actionSubscriptionArticlesFragmentToSubscriptionArticlesDetailFragment(articleId)
view?.findNavController()?.navigate(direction)
}
view?.findNavController()?.navigate(direction)
true
} ?: false
private fun toggleProgressCircle(articles: Articles) {
fun toggleArticleReadStatus(position: Int): Promise<Unit, Exception> = adapter.getItem(position)?.let {
Store.postReadStatus(it.article, it.article.readStatus.toggle())
} ?: Promise.ofSuccess(Unit)
private fun toggleProgressCircle(articles: List<*>) {
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)
READ -> context?.getString(R.string.empty_subscription_list, model.subscription.title)
UNREAD -> context?.getString(R.string.empty_subscription_unread_list, model.subscription.title)
}
if (Store.refreshingPromise.value == null) {
......@@ -80,4 +89,30 @@ open class SubscriptionArticlesFragment : Fragment(), Observer<Articles> {
fragment_subscription_article_empty_list.visibility = View.GONE
}
}
private inner class ArticleAdapter(items: List<SubscriptionArticleViewItem>) :
FlexibleAdapter<SubscriptionArticleViewItem>(items, null, true) {
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
super.onAttachedToRecyclerView(recyclerView)
isSwipeEnabled = true
itemTouchHelperCallback.setSwipeFlags(ItemTouchHelper.RIGHT)
addListener(OnItemClickListener { _, position -> this@SubscriptionArticlesFragment.onItemClick(position) })
addListener(object : OnItemSwipeListener {
override fun onActionStateChanged(viewHolder: ViewHolder?, actionState: Int) {}
override fun onItemSwipe(position: Int, direction: Int) {
if (direction == ItemTouchHelper.RIGHT) toggleArticleReadStatus(position).alwaysUi {
if (readStatus == READ) this@ArticleAdapter.notifyItemChanged(position)
}
}
})
}
override fun getItemId(position: Int) =
getItem(position)?.article?.id?.hashCode()?.toLong() ?: RecyclerView.NO_ID
override fun isEmpty(): Boolean = (model.liveData.value ?: listOf()).isEmpty()
}
}
......@@ -13,8 +13,9 @@ import fr.chenry.android.freshrss.store.database.models.Subscriptions
import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.task
class SubscriptionsFlexibleAdapter(private val fragment: SubscriptionsFragment) :
FlexibleAdapter<AbstractSubscriptionViewItem>(initFlexAdapter(fragment)), OnItemClickListener {
class SubscriptionsFlexibleAdapter(private val fragment: SubscriptionsFragment):
FlexibleAdapter<AbstractSubscriptionViewItem>(initFlexAdapter(fragment), null, true),
OnItemClickListener {
private val mediator = MediatorLiveData<List<AbstractSubscriptionViewItem>>()
private val subscriptionCategories
get() = fragment.model.subscriptionCategoriesLiveData.value ?: listOf()
......@@ -79,6 +80,14 @@ class SubscriptionsFlexibleAdapter(private val fragment: SubscriptionsFragment)
result
}
override fun getItemId(position: Int): Long = getGenericItem(position)?.let {
when(it) {
is SubscriptionViewItem -> it.subscription.id.hashCode().toLong()
is SubscriptionCategoryViewItem -> it.subscriptionCategory.id.hashCode().toLong()
else -> super.getItemId(position)
}
} ?: super.getItemId(position)
companion object {
private fun initFlexAdapter(fragment: SubscriptionsFragment) =
getSubscriptionViewItems(fragment.model.liveData.value ?: listOf(), fragment.model.grouper)
......
......@@ -20,6 +20,7 @@ import nl.komponents.kovenant.functional.bind
import nl.komponents.kovenant.task
import nl.komponents.kovenant.then
import nl.komponents.kovenant.toSuccessVoid
import java.util.concurrent.ConcurrentHashMap
import kotlin.math.max
object Store {
......@@ -32,6 +33,7 @@ object Store {
private var lastFetchTimestamp = 0L
private var api: Api
private val accountLiveData: LiveData<List<Account>>
private val onGoingPostRequests = ConcurrentHashMap<String, Promise<Unit, Exception>>()
init {
val flowable = FreshRSSApplication.database.getAccount()
......@@ -102,23 +104,28 @@ object Store {
all(promises, cancelOthersOnError = false).toSuccessVoid()
}
fun postReadStatus(article: Article, readStatus: ReadStatus): Promise<Unit, Exception> =
ensureToken() bind {
api.postReadStatus(article.id, readStatus)
.success {
FreshRSSApplication.database.apply {
upsertArticle(article.copy(readStatus = readStatus))
when (readStatus) {
READ -> {
decrementSubscriptionCount(article.streamId)
totalUnreadCount.value = max((totalUnreadCount.value ?: 0) - 1, 0)
}
UNREAD -> {
incrementSubscriptionCount(article.streamId)
totalUnreadCount.value = (totalUnreadCount.value ?: 0) + 1
}
}
fun postReadStatus(article: Article, readStatus: ReadStatus): Promise<Unit, Exception> {
if (onGoingPostRequests.contains(article.id)) return onGoingPostRequests[article.id]!!
return ensureToken() bind {
article.requestOnGoing = true
api.postReadStatus(article.id, readStatus).always {
article.requestOnGoing = false
onGoingPostRequests.remove(article.id)
}.success {
val db = FreshRSSApplication.database
db.upsertArticle(article.copy(readStatus = readStatus))
when (readStatus) {
READ -> {
db.decrementSubscriptionCount(article.streamId)
totalUnreadCount.postValue(max((totalUnreadCount.value ?: 0) - 1, 0))
}
UNREAD -> {
db.incrementSubscriptionCount(article.streamId)
totalUnreadCount.postValue((totalUnreadCount.value ?: 0) + 1)
}
}
}
}
}
}
......@@ -21,6 +21,7 @@ data class ContentItem(
@JsonProperty("timestampUsec")
@JsonDeserialize(using = MicroSecTimestampDeserializer::class)
val timestamp: LocalDateTime,
@JsonDeserialize(using = PublishedDateDeserializer::class)
val published: LocalDateTime,
val title: String,
val alternate: List<Href>,
......
......@@ -18,3 +18,7 @@ class MilliSecTimestampDeserializer : LocalDateTimeDeserializer() {
return LocalDateTime(p?.valueAsString?.toLongOrNull() ?: 0, tz)
}
}
class PublishedDateDeserializer : LocalDateTimeDeserializer() {
override fun deserialize(p: JsonParser?, ctxt: DeserializationContext?): LocalDateTime =
LocalDateTime((p?.valueAsLong ?: 0L) * 1000L)
}
......@@ -3,6 +3,7 @@ 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.Dao
import androidx.room.Entity
......@@ -12,6 +13,7 @@ import androidx.room.OnConflictStrategy
import androidx.room.PrimaryKey
import androidx.room.Query
import androidx.room.Update
import fr.chenry.android.freshrss.BR
import fr.chenry.android.freshrss.store.api.models.ContentItem
import fr.chenry.android.freshrss.store.api.models.StreamId
import fr.chenry.android.freshrss.utils.Try
......@@ -39,6 +41,14 @@ data class Article(
@Ignore
val url = Try { Uri.parse(href) }.getOrNull()
@Ignore
@Bindable
var requestOnGoing = false
set(value) {
field = value
notifyPropertyChanged(BR.requestOnGoing)
}
companion object {
fun fromContentItem(item: ContentItem) = Article(
item.id,
......
package fr.chenry.android.freshrss.store.database.models
import android.graphics.Bitmap
import android.widget.ImageView
import androidx.databinding.BaseObservable
import androidx.databinding.Bindable
import androidx.databinding.BindingAdapter
import androidx.databinding.library.baseAdapters.BR
import androidx.room.ColumnInfo
import androidx.room.Dao
......@@ -17,8 +15,6 @@ import com.squareup.picasso.Picasso
import fr.chenry.android.freshrss.R
import fr.chenry.android.freshrss.store.api.models.SubscriptionApiItem
import fr.chenry.android.freshrss.utils.nullIfBlank
import fr.chenry.android.freshrss.utils.whenNotNull
import fr.chenry.android.freshrss.utils.whenNull
import io.reactivex.Flowable
import org.joda.time.LocalDateTime
......@@ -64,12 +60,6 @@ fun Subscription?.areSimilar(other: Subscription?): Boolean {
this.subscriptionCategories == other.subscriptionCategories
}
@BindingAdapter("android:src")
fun setImageViewResource(imageView: ImageView, resource: Bitmap?) =
resource
.whenNotNull { imageView.setImageBitmap(resource) }
.whenNull { imageView.setImageResource(R.drawable.ic_rss_feed_black_24dp) }
@Dao
interface SubscriptionsDAO {
@Insert(onConflict = OnConflictStrategy.ABORT)
......
......@@ -6,13 +6,15 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.toLiveData
import fr.chenry.android.freshrss.FreshRSSApplication
import fr.chenry.android.freshrss.components.subscriptionarticles.SubscriptionArticleViewItem
import fr.chenry.android.freshrss.store.database.models.Article
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.Subscription
import fr.chenry.android.freshrss.store.database.models.Subscriptions
class SubscriptionArticlesVM(streamId: String, readStatus: ReadStatus) : ViewModel() {
val liveData: LiveData<Articles>
val liveData: LiveData<List<SubscriptionArticleViewItem>>
var subscription: Subscription
private set
......@@ -20,6 +22,12 @@ class SubscriptionArticlesVM(streamId: String, readStatus: ReadStatus) : ViewMod
private val source: LiveData<Articles>
init {
val comparator = Comparator<Article> { o1, o2 ->
if (o1.published.isEqual(o2.published))
o1.title.compareTo(o2.title, ignoreCase = true) else
o2.published.compareTo(o1.published)
}
val flowable = FreshRSSApplication.database.getSubcriptionsById(streamId)
subscription = flowable.blockingFirst().first()
subscriptionLiveData = flowable.toLiveData()
......@@ -30,9 +38,11 @@ class SubscriptionArticlesVM(streamId: String, readStatus: ReadStatus) : ViewMod
ReadStatus.UNREAD -> FreshRSSApplication.database.getArticleByStreamIdAndUnread(streamId)
}
liveData = MutableLiveData<Articles>().apply {
liveData = MutableLiveData<List<SubscriptionArticleViewItem>>().apply {
value = listOf()
source.observeForever { value = it.sortedByDescending { article -> article.published } }
source.observeForever {
value = it.sortedWith(comparator).map { article -> SubscriptionArticleViewItem(article, subscription) }
}
}
}
}
......
package fr.chenry.android.freshrss.utils
import android.graphics.Bitmap
import android.graphics.Typeface
import android.widget.ImageView
import android.widget.TextView
import androidx.databinding.BindingAdapter
import fr.chenry.android.freshrss.R
@BindingAdapter("font")
fun textStyle(textView: TextView, typeface: String) {
textView.typeface = when (typeface) {
"bold" -> Typeface.create(textView.typeface, Typeface.BOLD)
"italic" -> Typeface.create(textView.typeface, Typeface.ITALIC)
"bold_italic" -> Typeface.create(textView.typeface, Typeface.BOLD_ITALIC)
else -> Typeface.create(textView.typeface, Typeface.NORMAL)
}
}
@BindingAdapter("android:src")
fun setImageViewResource(imageView: ImageView, resource: Bitmap?) =
resource
.whenNotNull { imageView.setImageBitmap(resource) }
.whenNull { imageView.setImageResource(R.drawable.ic_rss_feed_black_24dp) }
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="article"
......@@ -7,28 +9,71 @@
<variable
name="subscription"
type="fr.chenry.android.freshrss.store.database.models.Subscription" />
<import type="fr.chenry.android.freshrss.store.database.models.ReadStatus" />
<import type="android.view.View" />
<import type="android.graphics.Typeface" />
</data>
<LinearLayout
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
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_height="wrap_content">
<LinearLayout
android:id="@+id/fragment_subscription_article_rear_left_view"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceListItem"
android:maxLines="1"
android:ellipsize="end" />
<TextView
android:text="@{subscription.title, default=`Author`}"
android:layout_height="match_parent"
android:paddingStart="@dimen/subscription_section_h_padding"
android:paddingEnd="@dimen/subscription_section_h_padding"
android:layout_gravity="start|center_vertical"
android:visibility="invisible"
android:background="@{article.readStatus.equals(ReadStatus.READ) ? @color/color_primary : @color/alert, default=@color/color_primary}">
<fr.chenry.android.freshrss.utils.SquaredImageView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:paddingTop="@dimen/text_margin"
android:paddingBottom="@dimen/text_margin"
android:src="@{article.readStatus.equals(ReadStatus.READ) ? @drawable/ic_is_unread_24dp : @drawable/ic_is_read_24dp, default=@drawable/ic_is_read_24dp}" />
<ProgressBar
style="?android:attr/progressBarStyle"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="100"
android:layout_gravity="center"
android:padding="0dp"
android:visibility="@{article.requestOnGoing ? View.VISIBLE : View.INVISIBLE, default=invisible}"
android:indeterminate="true"
android:indeterminateTintMode="src_atop"
android:indeterminateTint="@color/design_default_color_background" />
</LinearLayout>
<LinearLayout
android:id="@+id/fragment_subscription_article_front_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceListItem"
android:textSize="12sp"
android:textStyle="bold" />
</LinearLayout>
</layout>
\ No newline at end of file
android:orientation="vertical"
android:paddingStart="@dimen/subscription_section_h_padding"
android:paddingEnd="@dimen/subscription_section_h_padding"
android:paddingTop="@dimen/text_margin"
android:paddingBottom="@dimen/text_margin"
android:background="@color/design_default_color_background"
android:visibility="visible">
<TextView
android:text="@{article.title, default=`Title`}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceListItem"
android:textStyle="normal"
app:font="@{article.readStatus.equals(ReadStatus.READ) ? `normal` : `bold`}"
android:maxLines="1"
android:ellipsize="end" />
<TextView
android:text="@{subscription.title, default=`Author`}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceListItem"
android:textSize="12sp"
android:textStyle="italic"
app:font="@{article.readStatus.equals(ReadStatus.READ) ? `italic` : `bold_italic`}" />
</LinearLayout>
</FrameLayout>
</layout>
......@@ -12,8 +12,6 @@
android:id="@+id/fragment_subscription_article_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
tools:listitem="@layout/fragment_subscription_article"
android:visibility="visible" />
<LinearLayout
......
......@@ -34,3 +34,5 @@ task clean(type: Delete) {
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
kotlinOptions.freeCompilerArgs += ["-Xuse-experimental=kotlin.Experimental"]
}
task lintAllFix([dependsOn: ["app:lintFix", "app:spotlessApply"]]) {}
\ No newline at end of file
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