Commit 65f48ded authored by Christophe Henry's avatar Christophe Henry Committed by Christophe Henry

Solves #9: Implement pull-to-refresh

parent a0a0b690
...@@ -77,7 +77,7 @@ dependencies { ...@@ -77,7 +77,7 @@ dependencies {
implementation 'androidx.recyclerview:recyclerview:1.0.0' implementation 'androidx.recyclerview:recyclerview:1.0.0'
// ViewModel and LiveData // ViewModel and LiveData
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.0.0' implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-reactivestreams-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-reactivestreams-ktx:$lifecycle_version"
annotationProcessor "androidx.lifecycle:lifecycle-compiler:$lifecycle_version" annotationProcessor "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
......
...@@ -4,9 +4,7 @@ import android.app.Notification ...@@ -4,9 +4,7 @@ import android.app.Notification
import android.app.Service import android.app.Service
import android.content.Intent import android.content.Intent
import android.os.Binder import android.os.Binder
import android.widget.RemoteViews
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.lifecycle.MutableLiveData
import fr.chenry.android.freshrss.store.Store import fr.chenry.android.freshrss.store.Store
import fr.chenry.android.freshrss.utils.NotificationChanels import fr.chenry.android.freshrss.utils.NotificationChanels
import fr.chenry.android.freshrss.utils.NotificationHelper import fr.chenry.android.freshrss.utils.NotificationHelper
...@@ -17,7 +15,6 @@ import nl.komponents.kovenant.ui.failUi ...@@ -17,7 +15,6 @@ import nl.komponents.kovenant.ui.failUi
import nl.komponents.kovenant.ui.successUi import nl.komponents.kovenant.ui.successUi
class RefresherService: Service() { class RefresherService: Service() {
private val refreshingPromise = MutableLiveData<Promise<Unit, Exception>>()
private val refreshNotification = private val refreshNotification =
createNotification(R.string.notification_refresh_title, R.string.notification_refresh_description) createNotification(R.string.notification_refresh_title, R.string.notification_refresh_description)
.setCategory(Notification.CATEGORY_PROGRESS) .setCategory(Notification.CATEGORY_PROGRESS)
...@@ -30,7 +27,7 @@ class RefresherService: Service() { ...@@ -30,7 +27,7 @@ class RefresherService: Service() {
override fun onBind(intent: Intent) = RefresherBinder() override fun onBind(intent: Intent) = RefresherBinder()
fun refresh(): Promise<Unit, Exception> { fun refresh(): Promise<Unit, Exception> {
if(refreshingPromise.value != null) return refreshingPromise.value!! if(Store.refreshingPromise.value != null) return Store.refreshingPromise.value!!
this.startForeground(NotificationHelper.ONGOING_REFRESH_NOTIFICATION, refreshNotification) this.startForeground(NotificationHelper.ONGOING_REFRESH_NOTIFICATION, refreshNotification)
...@@ -41,8 +38,8 @@ class RefresherService: Service() { ...@@ -41,8 +38,8 @@ class RefresherService: Service() {
.bind {all(Store.subscriptions.keys.map {Store.getStreamContents(it)}, cancelOthersOnError = false)} .bind {all(Store.subscriptions.keys.map {Store.getStreamContents(it)}, cancelOthersOnError = false)}
.bind {Store.getUnreadItems()} .bind {Store.getUnreadItems()}
refreshingPromise.postValue(promise) Store.refreshingPromise.postValue(promise)
promise.always {refreshingPromise.postValue(null)} promise.always {Store.refreshingPromise.postValue(null)}
.successUi {stopForeground(true)} .successUi {stopForeground(true)}
.failUi { .failUi {
FreshRSSApplication FreshRSSApplication
......
...@@ -12,13 +12,10 @@ import fr.chenry.android.freshrss.components.subscriptions.SubscriptionSection.* ...@@ -12,13 +12,10 @@ import fr.chenry.android.freshrss.components.subscriptions.SubscriptionSection.*
import fr.chenry.android.freshrss.store.Store import fr.chenry.android.freshrss.store.Store
import fr.chenry.android.freshrss.utils.whenNotNull import fr.chenry.android.freshrss.utils.whenNotNull
import kotlinx.android.synthetic.main.fragment_main_subscription.* import kotlinx.android.synthetic.main.fragment_main_subscription.*
import nl.komponents.kovenant.ui.alwaysUi
class MainSubscriptionFragment: Fragment(), BottomNavigationView.OnNavigationItemSelectedListener { class MainSubscriptionFragment: Fragment(), BottomNavigationView.OnNavigationItemSelectedListener {
private val adapter by lazy {MainSubscriptionPagerAdapter()} private val adapter by lazy {MainSubscriptionPagerAdapter()}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
inflater.inflate(R.layout.fragment_main_subscription, container, false) inflater.inflate(R.layout.fragment_main_subscription, container, false)
...@@ -26,6 +23,12 @@ class MainSubscriptionFragment: Fragment(), BottomNavigationView.OnNavigationIte ...@@ -26,6 +23,12 @@ class MainSubscriptionFragment: Fragment(), BottomNavigationView.OnNavigationIte
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
subcription_pull_to_refresh.setOnRefreshListener {
FreshRSSApplication.application.refresherService.whenNotNull {
it.refresh().alwaysUi {subcription_pull_to_refresh.isRefreshing = false}
}
}
subcription_fragment_container.adapter = adapter subcription_fragment_container.adapter = adapter
subcription_fragment_container.offscreenPageLimit = SubscriptionSection.values().size subcription_fragment_container.offscreenPageLimit = SubscriptionSection.values().size
...@@ -39,15 +42,6 @@ class MainSubscriptionFragment: Fragment(), BottomNavigationView.OnNavigationIte ...@@ -39,15 +42,6 @@ class MainSubscriptionFragment: Fragment(), BottomNavigationView.OnNavigationIte
subcription_bottom_navigation.setOnNavigationItemSelectedListener(this) subcription_bottom_navigation.setOnNavigationItemSelectedListener(this)
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.main_actionbar, menu)
menu.findItem(R.id.action_refresh)?.setOnMenuItemClickListener {
FreshRSSApplication.application.refresherService.whenNotNull {it.refresh()}.let {true}
}
super.onCreateOptionsMenu(menu, inflater)
}
override fun onNavigationItemSelected(it: MenuItem): Boolean { override fun onNavigationItemSelected(it: MenuItem): Boolean {
SubscriptionSection.fromNavigationButton(it.itemId).let { SubscriptionSection.fromNavigationButton(it.itemId).let {
Store.subscriptionsSection.value = it Store.subscriptionsSection.value = it
......
...@@ -38,6 +38,7 @@ class SubscriptionsFragment: Fragment(), Observer<Subscriptions> { ...@@ -38,6 +38,7 @@ class SubscriptionsFragment: Fragment(), Observer<Subscriptions> {
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_subscriptions, container, false) val view = inflater.inflate(R.layout.fragment_subscriptions, container, false)
Store.refreshingPromise.observe(this, Observer {toggleProgressCircle()})
model.liveData.observe(this, this) model.liveData.observe(this, this)
view.findViewById<RecyclerView>(R.id.subscriptions_list).let { view.findViewById<RecyclerView>(R.id.subscriptions_list).let {
...@@ -50,8 +51,7 @@ class SubscriptionsFragment: Fragment(), Observer<Subscriptions> { ...@@ -50,8 +51,7 @@ class SubscriptionsFragment: Fragment(), Observer<Subscriptions> {
override fun onChanged(subscriptions: Subscriptions?) { override fun onChanged(subscriptions: Subscriptions?) {
(subscriptions ?: listOf()).let { (subscriptions ?: listOf()).let {
subscriptions_list.visibility = if(it.isEmpty()) View.GONE else View.VISIBLE toggleProgressCircle()
fragment_subscriptions_waiting.visibility = if(it.isNotEmpty()) View.GONE else View.VISIBLE
adapter.notifyDataSetChanged() adapter.notifyDataSetChanged()
} }
} }
...@@ -64,6 +64,12 @@ class SubscriptionsFragment: Fragment(), Observer<Subscriptions> { ...@@ -64,6 +64,12 @@ class SubscriptionsFragment: Fragment(), Observer<Subscriptions> {
}.let {view?.findNavController()?.navigate(it)} }.let {view?.findNavController()?.navigate(it)}
} }
private fun toggleProgressCircle() {
subscriptions_list.visibility =
if(subscriptions.isEmpty() && Store.refreshingPromise.value != null)View.GONE else View.VISIBLE
fragment_subscriptions_waiting.visibility = if(subscriptions.isNotEmpty()) View.GONE else View.VISIBLE
}
companion object { companion object {
const val argumentKey = "subscriptionSection" const val argumentKey = "subscriptionSection"
} }
......
...@@ -22,6 +22,7 @@ object Store { ...@@ -22,6 +22,7 @@ object Store {
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 tags = MutableLiveData<List<String>>().apply {this.value = listOf()}
val favorites = GenericLiveData<String, Subscription>(mutableMapOf()) val favorites = GenericLiveData<String, Subscription>(mutableMapOf())
val refreshingPromise = MutableLiveData<Promise<Unit, Exception>>()
private var lastFetchTimestamp = 0L private var lastFetchTimestamp = 0L
fun init(account: Account) { fun init(account: Account) {
......
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFF"
android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z"/>
</vector>
...@@ -8,16 +8,20 @@ ...@@ -8,16 +8,20 @@
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".components.subscriptions.MainSubscriptionFragment"> tools:context=".components.subscriptions.MainSubscriptionFragment">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
<androidx.viewpager.widget.ViewPager android:id="@+id/subcription_pull_to_refresh"
android:id="@+id/subcription_fragment_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
app:layout_constraintBottom_toTopOf="@+id/subcription_bottom_navigation" app:layout_constraintBottom_toTopOf="@+id/subcription_bottom_navigation"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent"
</androidx.viewpager.widget.ViewPager> tools:context=".components.subscriptions.SubscriptionsFragment">
<androidx.viewpager.widget.ViewPager
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/subcription_fragment_container" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<com.google.android.material.bottomnavigation.BottomNavigationView <com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/subcription_bottom_navigation" android:id="@+id/subcription_bottom_navigation"
...@@ -29,5 +33,5 @@ ...@@ -29,5 +33,5 @@
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
app:menu="@menu/navigation" /> app:menu="@menu/subscriptions_bottom_navigation" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/action_refresh"
android:orderInCategory="100"
android:icon="@drawable/ic_refresh_white_24dp"
android:title="@string/refresh"
app:showAsAction="always" />
</menu>
\ 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