Commit 72ec5e61 authored by Christophe Henry's avatar Christophe Henry

Refacto subscriptions fragment

parent 1716bcb5
package fr.chenry.android.freshrss.activities
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
......@@ -40,11 +39,6 @@ class MainActivity: AppCompatActivity() {
deferred.promise successUi {Router.navigate(RouteName.SUBSCRIPTIONS, pushHistory = false)} failUi {this.e(it)}
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
if(item == null) return super.onOptionsItemSelected(item)
return when(item.itemId) {
......@@ -66,8 +60,10 @@ class MainActivity: AppCompatActivity() {
to.parameters.entries.forEach {(k, v) -> this.putString(k, v)}
}
val fragmentTransaction = supportFragmentManager?.beginTransaction()
?.replace(R.id.fragment_container, Fragment.instantiate(this, to.fragment.qualifiedName, bundle))
val fragmentTransaction = supportFragmentManager
?.beginTransaction()
?.replace(R.id.fragment_container,
Fragment.instantiate(this, to.fragment.qualifiedName, bundle))
if(to.pushHistory)
fragmentTransaction?.addToBackStack(null)
......
package fr.chenry.android.freshrss.components.subscriptions
import fr.chenry.android.freshrss.store.Store
import fr.chenry.android.freshrss.store.dao.common.Subscriptions
class AllSubscriptionsFragment: SubscriptionsFragment() {
override val subscriptions: Subscriptions get() = Store.subscriptions.values
}
......@@ -6,21 +6,27 @@ import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import fr.chenry.android.freshrss.R
import fr.chenry.android.freshrss.store.*
import kotlinx.android.synthetic.main.fragment_subscriptions.*
import fr.chenry.android.freshrss.store.Store
import fr.chenry.android.freshrss.store.dao.common.Subscriptions
import kotlinx.android.synthetic.main.fragment_main_subscription.*
import kotlin.reflect.KClass
class SubscriptionsFragment: Fragment() {
private lateinit var recyclerViewAdapter: SubscriptionsRecyclerViewAdapter
class MainSubscriptionFragment: Fragment() {
private val fragment: KClass<out SubscriptionsFragment>
get() = when(Store.subscriptionsSection.value) {
SubscriptionSection.ALL -> AllSubscriptionsFragment::class
SubscriptionSection.FAVORITES -> AllSubscriptionsFragment::class
SubscriptionSection.UNREAD -> UnreadSubscriptionsFragment::class
else -> TODO("Handle bad section")
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_subscriptions, container, false)
val recyclerView = view.findViewById<RecyclerView>(R.id.subscriptions_list)
val view = inflater.inflate(R.layout.fragment_main_subscription, container, false)
with(recyclerView) {
recyclerViewAdapter = SubscriptionsRecyclerViewAdapter()
layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
adapter = recyclerViewAdapter
}
activity?.supportFragmentManager
?.beginTransaction()
?.add(R.id.subcription_fragment_container, Fragment.instantiate(activity, fragment.qualifiedName))
?.commit()
return view
}
......@@ -40,8 +46,27 @@ class SubscriptionsFragment: Fragment() {
R.id.subscriptions_bottom_navigation_favorites -> SubscriptionSection.FAVORITES
else -> SubscriptionSection.ALL
}
recyclerViewAdapter.emit()
activity?.supportFragmentManager
?.beginTransaction()
?.replace(R.id.subcription_fragment_container, Fragment.instantiate(activity, fragment.qualifiedName))
?.commit()
true
}
}
}
abstract class SubscriptionsFragment: Fragment() {
abstract val subscriptions: Subscriptions
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_subscriptions, container, false)
view.findViewById<RecyclerView>(R.id.subscriptions_list).let {
it.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
it.adapter = RecyclerViewAdapter(this)
}
return view
}
}
\ No newline at end of file
......@@ -10,9 +10,9 @@ import fr.chenry.android.freshrss.databinding.FragmentSubscriptionBinding
import fr.chenry.android.freshrss.store.*
import fr.chenry.android.freshrss.store.dao.common.Subscription
class SubscriptionsRecyclerViewAdapter: RecyclerView.Adapter<SubscriptionsRecyclerViewAdapter.ViewHolder>() {
private var subscriptions = refreshitems()
class RecyclerViewAdapter(private val fragment: SubscriptionsFragment):
RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder>()
{
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
......@@ -22,22 +22,12 @@ class SubscriptionsRecyclerViewAdapter: RecyclerView.Adapter<SubscriptionsRecycl
return ViewHolder(binding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(subscriptions[position])
override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(fragment.subscriptions[position])
@UiThread
fun emit() {
subscriptions = refreshitems()
notifyDataSetChanged()
}
override fun getItemCount(): Int = subscriptions.size
fun emit() = notifyDataSetChanged()
private fun refreshitems() = when(Store.subscriptionsSection.value) {
SubscriptionSection.FAVORITES -> Store.favorites
SubscriptionSection.ALL -> Store.subscriptions
SubscriptionSection.UNREAD -> Store.unreadSubscriptions
else -> Store.unreadSubscriptions
}.values
override fun getItemCount(): Int = fragment.subscriptions.size
inner class ViewHolder(private val binding: FragmentSubscriptionBinding):
RecyclerView.ViewHolder(binding.root) {
......
package fr.chenry.android.freshrss.components.subscriptions
import android.os.Bundle
import android.view.*
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import fr.chenry.android.freshrss.R
import fr.chenry.android.freshrss.store.dao.common.Subscriptions
import fr.chenry.android.freshrss.store.databindingsupport.viewmodels.UnreadContentViewModel
class UnreadSubscriptionsFragment: SubscriptionsFragment() {
private lateinit var model: UnreadContentViewModel
override val subscriptions: Subscriptions get() = model.subscriptions
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activity?.run {
model = ViewModelProviders.of(this).get(UnreadContentViewModel::class.java)
}
}
}
......@@ -5,11 +5,18 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.MutableLiveData
import fr.chenry.android.freshrss.components.subscriptioncontents.SubscriptionContentDetailFragment
import fr.chenry.android.freshrss.components.subscriptioncontents.SubscriptionContentsFragment
import fr.chenry.android.freshrss.components.subscriptions.SubscriptionsFragment
import fr.chenry.android.freshrss.components.subscriptions.MainSubscriptionFragment
import fr.chenry.android.freshrss.components.subscriptions.UnreadSubscriptionsFragment
import fr.chenry.android.freshrss.store.Router.Route
import kotlin.reflect.KClass
enum class RouteName {
SUBSCRIPTIONS, SUBSCRIPTION_CONTENTS, SUBSCRIPTION_CONTENT_DETAIL
SUBSCRIPTIONS,
SUBSCRIPTIONS_ALL,
SUBSCRIPTIONS_UNREAD,
SUBSCRIPTIONS_FAVORITES,
SUBSCRIPTION_CONTENTS,
SUBSCRIPTION_CONTENT_DETAIL
}
object Router {
......@@ -58,7 +65,7 @@ object Router {
}
private val routes = listOf(
Router.Route("/subscriptions", RouteName.SUBSCRIPTIONS, SubscriptionsFragment::class),
Router.Route("/subscription/:id", RouteName.SUBSCRIPTION_CONTENTS, SubscriptionContentsFragment::class),
Router.Route("/subscription/:streamId/article/:articleId", RouteName.SUBSCRIPTION_CONTENT_DETAIL, SubscriptionContentDetailFragment::class)
Route("/subscriptions", RouteName.SUBSCRIPTIONS, MainSubscriptionFragment::class),
Route("/subscription/:id", RouteName.SUBSCRIPTION_CONTENTS, SubscriptionContentsFragment::class),
Route("/subscription/:streamId/article/:articleId", RouteName.SUBSCRIPTION_CONTENT_DETAIL, SubscriptionContentDetailFragment::class)
)
......@@ -17,18 +17,9 @@ object Store {
var debugMode = false
val api = Api()
val subscriptions = GenericLiveData<String, Subscription>()
val unreadSubscriptions: GenericLiveData<String, Subscription> by lazy {
GenericLiveData<String, Subscription>().let {
val subscriptions = Store.subscriptions
if(subscriptions.isNullOrEmpty()) return@let GenericLiveData<String, Subscription>()
GenericLiveData(subscriptions.filter {it.value.unreadCount > 0})
}
}
val totalUnreadCount = MutableLiveData<Int>()
val contentItems = GenericLiveData<String, ContentItems>()
val subscriptionsSection = MutableLiveData<SubscriptionSection>()
.apply {this.value = SubscriptionSection.ALL}
val subscriptionsSection = MutableLiveData<SubscriptionSection>().apply {this.value = SubscriptionSection.ALL}
val tags = MutableLiveData<List<String>>().apply {this.value = listOf()}
val favorites = GenericLiveData<String, Subscription>(mutableMapOf())
private var lastFetchTimestamp = 0L
......
......@@ -3,54 +3,29 @@ package fr.chenry.android.freshrss.store.databindingsupport
import androidx.lifecycle.LiveData
class GenericLiveData<T, U>
constructor(items: Map<T, U>):
LiveData<GenericLiveData<T, U>>(), List<U>, Map<T, U>
{
private var list = items.values.toMutableList()
private var map = mutableMapOf<T, U>().apply { this.putAll(items) }
constructor(vararg items: Pair<T, U>): this(items.toList().toMap())
constructor(): this(mutableMapOf<T, U>())
@Synchronized override operator fun get(key: T) = map[key]
@Synchronized operator fun set(key: T, value: U) { addAll(mapOf(key to value)) }
fun emit() = super.postValue(this)
fun emitUi() = super.setValue(this)
class GenericLiveData<T, U>(items: Map<T, U>): LiveData<MapList<T, U>>(){
private val mapList = MapList(items)
fun filter(predicate: (Map.Entry<T, U>) -> Boolean): GenericLiveData<T, U> = GenericLiveData(map.filter(predicate))
fun isNullOrEmpty() = map.isNullOrEmpty()
constructor(): this(mutableMapOf<T, U>())
fun emit() = super.postValue(mapList)
fun emitUi() = super.setValue(mapList)
fun isNullOrEmpty() = mapList.isNullOrEmpty()
fun filter(predicate: (Map.Entry<T, U>) -> Boolean) = GenericLiveData(mapList.filter(predicate))
fun addAll(items: Map<T, U>) {
items.forEach {
if(map.contains(it.key)) map[it.key] = it.value
else {
list.add(it.value)
map[it.key] = it.value
}
}
mapList.addAll(items)
this.emit()
}
fun addAll(items: List<Pair<T, U>>) = addAll(items.toMap())
fun addAll(vararg item: Pair<T, U>) = addAll(item.toMap())
override val size get() = list.size
override fun contains(element: U) = list.contains(element)
override fun containsAll(elements: Collection<U>) = list.containsAll(elements)
override fun get(index: Int) = list[index]
override fun indexOf(element: U) = list.indexOf(element)
override fun isEmpty() = list.isEmpty()
override fun iterator() = list.iterator()
override fun lastIndexOf(element: U) = list.lastIndexOf(element)
override fun listIterator() = list.listIterator()
override fun listIterator(index: Int) = list.listIterator(index)
override fun subList(fromIndex: Int, toIndex: Int) = list.subList(fromIndex, toIndex)
override val entries get() = map.entries
override val keys get() = map.keys
override val values get() = list.toList()
override fun containsKey(key: T) = map.containsKey(key)
override fun containsValue(value: U) = map.containsValue(value)
operator fun get(key: T) = mapList[key]
operator fun get(index: Int) = mapList[index]
operator fun set(key: T, value: U) = addAll(mapOf(key to value))
val size get() = mapList.size
val entries get() = mapList.entries
val keys get() = mapList.keys
val values get() = mapList.values
}
package fr.chenry.android.freshrss.store.databindingsupport
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
class MapList<T, U>(items: Map<T, U>): List<U>, Map<T, U> {
private val lock = ReentrantLock()
private var list = items.values.toMutableList()
private var map = mutableMapOf<T, U>().apply { this.putAll(items) }
constructor(vararg items: Pair<T, U>): this(items.toList().toMap())
constructor(items: Collection<Map.Entry<T, U>>): this(items.map {it.key to it.value}.toMap())
constructor(): this(mutableMapOf<T, U>())
override operator fun get(key: T) = lock.withLock {map[key]}
operator fun set(key: T, value: U) { addAll(mapOf(key to value)) }
fun addAll(items: Map<T, U>) {
lock.withLock {
items.forEach {
if(map.contains(it.key)) map[it.key] = it.value
else {
list.add(it.value)
map[it.key] = it.value
}
}
}
}
fun addAll(items: List<Pair<T, U>>) = addAll(items.toMap())
fun addAll(vararg item: Pair<T, U>) = addAll(item.toMap())
fun filter(predicate: (Map.Entry<T, U>) -> Boolean): MapList<T, U> = lock.withLock {MapList(map.filter(predicate))}
fun isNullOrEmpty() = lock.withLock {map.isNullOrEmpty()}
override val size get() = lock.withLock {list.size}
override fun contains(element: U) = lock.withLock {list.contains(element)}
override fun containsAll(elements: Collection<U>) = lock.withLock {list.containsAll(elements)}
override fun get(index: Int) = lock.withLock {list[index]}
override fun indexOf(element: U) = lock.withLock {list.indexOf(element)}
override fun isEmpty() = lock.withLock {list.isEmpty()}
override fun iterator() = lock.withLock {list.iterator()}
override fun lastIndexOf(element: U) = lock.withLock {list.lastIndexOf(element)}
override fun listIterator() = lock.withLock {list.listIterator()}
override fun listIterator(index: Int) = lock.withLock {list.listIterator(index)}
override fun subList(fromIndex: Int, toIndex: Int) = lock.withLock {list.subList(fromIndex, toIndex)}
override val entries: Set<Map.Entry<T, U>> get() = lock.withLock {map.entries}
override val keys: Set<T> get() = lock.withLock {map.keys}
override val values: List<U> get() = lock.withLock {list.toList()}
override fun containsKey(key: T) = lock.withLock {map.containsKey(key)}
override fun containsValue(value: U) = lock.withLock {map.containsValue(value)}
}
\ No newline at end of file
package fr.chenry.android.freshrss.store.databindingsupport.viewmodels
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.ViewModel
import fr.chenry.android.freshrss.store.Store
import fr.chenry.android.freshrss.store.dao.common.Subscription
import fr.chenry.android.freshrss.store.dao.common.Subscriptions
import fr.chenry.android.freshrss.store.databindingsupport.MapList
class UnreadContentViewModel: ViewModel() {
private val liveData: MediatorLiveData<MapList<String, Subscription>> by lazy {
MediatorLiveData<MapList<String, Subscription>>().apply {this.value = load()}
}
val subscriptions: Subscriptions get() = liveData.value?.values ?: listOf()
init {
liveData.addSource(Store.subscriptions) {liveData.value = load()}
}
private fun load(): MapList<String, Subscription> {
if(Store.subscriptions.isNullOrEmpty()) return MapList()
return MapList(Store.subscriptions.entries.filter {it.value.unreadCount > 0})
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/subscriptions_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/subcription_fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toTopOf="@+id/subcription_bottom_navigation"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
</FrameLayout>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/subcription_bottom_navigation"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="0dp"
android:layout_marginStart="0dp"
android:background="?android:attr/windowBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="@menu/navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/subscriptions_container"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/subscriptions_list"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/subscriptions_list"
android:name="fr.chenry.android.freshrss.SubscriptionsFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toTopOf="@+id/subcription_bottom_navigation"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:context=".components.subscriptions.SubscriptionsFragment"
tools:context=".components.subscriptions.UnreadSubscriptionsFragment"
tools:listitem="@layout/fragment_subscription"
tools:itemCount="1" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/subcription_bottom_navigation"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="0dp"
android:layout_marginStart="0dp"
android:background="?android:attr/windowBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="@menu/navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
</LinearLayout>
\ 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