From 2c8902d4046d297b034365927254d0a36869df4a Mon Sep 17 00:00:00 2001 From: Amine Bou Date: Sat, 6 Jan 2018 15:33:58 +0100 Subject: [PATCH] Working on #169. List isn't reloaded each time. When new articles arrive, the list is still updated, but the list stays at the same (old) position. Not sure if I keep this behavior, or try to keep the old position and scroll to it. --- CHANGELOG.md | 2 + .../bou/readerforselfoss/HomeActivity.kt | 202 ++++++++++-------- .../adapters/ItemCardAdapter.kt | 92 +------- .../adapters/ItemListAdapter.kt | 91 +------- .../readerforselfoss/adapters/ItemsAdapter.kt | 121 +++++++++++ .../api/mercury/MercuryModels.kt | 4 +- .../fragments/ArticleFragment.kt | 6 +- 7 files changed, 256 insertions(+), 262 deletions(-) create mode 100644 app/src/main/java/apps/amine/bou/readerforselfoss/adapters/ItemsAdapter.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 185e7d0..4803e85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ - Updated the Contribution guide about translations. +- Better handling for articles update. (See #169) + **1.5.5.x (didn't last long) AND 1.5.6.x** - Toolbar in reader activity. diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/HomeActivity.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/HomeActivity.kt index 24cf10c..766282a 100644 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/HomeActivity.kt +++ b/app/src/main/java/apps/amine/bou/readerforselfoss/HomeActivity.kt @@ -17,13 +17,13 @@ import android.support.v7.widget.RecyclerView import android.support.v7.widget.SearchView import android.support.v7.widget.StaggeredGridLayoutManager import android.support.v7.widget.helper.ItemTouchHelper -import android.util.Log import android.view.Menu import android.view.MenuItem import android.view.View import android.widget.Toast import apps.amine.bou.readerforselfoss.adapters.ItemCardAdapter import apps.amine.bou.readerforselfoss.adapters.ItemListAdapter +import apps.amine.bou.readerforselfoss.adapters.ItemsAdapter import apps.amine.bou.readerforselfoss.api.selfoss.Item import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi import apps.amine.bou.readerforselfoss.api.selfoss.Sources @@ -119,6 +119,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener { private var recyclerViewScrollListener: RecyclerView.OnScrollListener? = null private lateinit var settings: SharedPreferences + private var recyclerAdapter: RecyclerView.Adapter<*>? = null + private var badgeNew: Int = -1 private var badgeAll: Int = -1 private var badgeFavs: Int = -1 @@ -161,7 +163,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener { handleBottomBar() handleDrawer() - reloadLayoutManager() handleSwipeRefreshLayout() } @@ -314,6 +315,16 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener { handleSharedPrefs() + reloadLayoutManager() + + if (!infiniteScroll) { + recyclerView.setHasFixedSize(true) + } else { + handleInfiniteScroll() + } + + handleBottomBarActions() + getElementsAccordingToTab() } @@ -623,48 +634,67 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener { } private fun reloadLayoutManager() { - val mLayoutManager: RecyclerView.LayoutManager - if (shouldBeCardView) { - mLayoutManager = StaggeredGridLayoutManager( - calculateNoOfColumns(), - StaggeredGridLayoutManager.VERTICAL - ) - mLayoutManager.gapStrategy = StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS - } else { - mLayoutManager = GridLayoutManager(this, calculateNoOfColumns()) + val currentManager = recyclerView.layoutManager + val layoutManager: RecyclerView.LayoutManager + + // This will only update the layout manager if settings changed + when (currentManager) { + is StaggeredGridLayoutManager -> + if (!shouldBeCardView) { + layoutManager = GridLayoutManager(this, calculateNoOfColumns()) + recyclerView.layoutManager = layoutManager + } + is GridLayoutManager -> + if (shouldBeCardView) { + layoutManager = StaggeredGridLayoutManager( + calculateNoOfColumns(), + StaggeredGridLayoutManager.VERTICAL + ) + layoutManager.gapStrategy = StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS + recyclerView.layoutManager = layoutManager + } + else -> + if (currentManager == null) { + if (!shouldBeCardView) { + layoutManager = GridLayoutManager(this, calculateNoOfColumns()) + recyclerView.layoutManager = layoutManager + } else { + layoutManager = StaggeredGridLayoutManager( + calculateNoOfColumns(), + StaggeredGridLayoutManager.VERTICAL + ) + layoutManager.gapStrategy = StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS + recyclerView.layoutManager = layoutManager + } + } else { + Unit + } } - - recyclerView.layoutManager = mLayoutManager - - if (!infiniteScroll) { - recyclerView.setHasFixedSize(true) - } else { - handleInfiniteScroll() - } - - handleBottomBarActions(mLayoutManager) } - private fun handleBottomBarActions(mLayoutManager: RecyclerView.LayoutManager) { + private fun handleBottomBarActions() { bottomBar.setTabSelectedListener(object : BottomNavigationBar.OnTabSelectedListener { override fun onTabUnselected(position: Int) = Unit - override fun onTabReselected(position: Int) = - when (mLayoutManager) { - is StaggeredGridLayoutManager -> - if (mLayoutManager.findFirstCompletelyVisibleItemPositions(null)[0] == 0) { - getElementsAccordingToTab() - } else { - mLayoutManager.scrollToPositionWithOffset(0, 0) - } - is GridLayoutManager -> - if (mLayoutManager.findFirstCompletelyVisibleItemPosition() == 0) { - getElementsAccordingToTab() - } else { - mLayoutManager.scrollToPositionWithOffset(0, 0) - } - else -> Unit - } + override fun onTabReselected(position: Int) { + val layoutManager = recyclerView.adapter + + when (layoutManager) { + is StaggeredGridLayoutManager -> + if (layoutManager.findFirstCompletelyVisibleItemPositions(null)[0] == 0) { + getElementsAccordingToTab() + } else { + layoutManager.scrollToPositionWithOffset(0, 0) + } + is GridLayoutManager -> + if (layoutManager.findFirstCompletelyVisibleItemPosition() == 0) { + getElementsAccordingToTab() + } else { + layoutManager.scrollToPositionWithOffset(0, 0) + } + else -> Unit + } + } override fun onTabSelected(position: Int) { offset = 0 @@ -739,25 +769,21 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener { call: (String?, Long?, String?) -> Call> ) { fun handleItemsResponse(response: Response>) { - val didUpdate = (response.body() != items) + val shouldUpdate = (response.body() != items) if (response.body() != null) { - if (response.body() != items) { - if (appendResults) { - items.addAll(response.body() as ArrayList) - } else { - items = response.body() as ArrayList - } + if (shouldUpdate) { + items = response.body() as ArrayList } } else { if (!appendResults) { items = ArrayList() } } - if (didUpdate) { + if (shouldUpdate) { handleListResult(appendResults) } - mayBeEmpty() + if (!appendResults) mayBeEmpty() swipeRefreshLayout.isRefreshing = false } @@ -836,49 +862,49 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener { } } - reloadLayoutManager() + if (recyclerAdapter == null) { + if (shouldBeCardView) { + recyclerAdapter = + ItemCardAdapter( + this, + items, + api, + customTabActivityHelper, + internalBrowser, + articleViewer, + fullHeightCards, + appColors, + debugReadingItems, + userIdentifier + ) + } else { + recyclerAdapter = + ItemListAdapter( + this, + items, + api, + customTabActivityHelper, + clickBehavior, + internalBrowser, + articleViewer, + debugReadingItems, + userIdentifier + ) - val mAdapter: RecyclerView.Adapter<*> - if (shouldBeCardView) { - mAdapter = - ItemCardAdapter( - this, - items, - api, - customTabActivityHelper, - internalBrowser, - articleViewer, - fullHeightCards, - appColors, - debugReadingItems, - userIdentifier - ) + recyclerView.addItemDecoration( + DividerItemDecoration( + this@HomeActivity, + DividerItemDecoration.VERTICAL + ) + ) + } + recyclerView.adapter = recyclerAdapter } else { - mAdapter = - ItemListAdapter( - this, - items, - api, - customTabActivityHelper, - clickBehavior, - internalBrowser, - articleViewer, - debugReadingItems, - userIdentifier - ) - - recyclerView.addItemDecoration( - DividerItemDecoration( - this@HomeActivity, - DividerItemDecoration.VERTICAL - ) - ) - } - recyclerView.adapter = mAdapter - mAdapter.notifyDataSetChanged() - - if (appendResults) { - recyclerView.scrollToPosition(firstVisible!!) + if (!appendResults) { + (recyclerAdapter as ItemsAdapter<*>).updateAllItems(items) + } else { + (recyclerAdapter as ItemsAdapter<*>).addItemsAtEnd(items) + } } reloadBadges() diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/adapters/ItemCardAdapter.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/adapters/ItemCardAdapter.kt index c46f73c..5ece514 100644 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/adapters/ItemCardAdapter.kt +++ b/app/src/main/java/apps/amine/bou/readerforselfoss/adapters/ItemCardAdapter.kt @@ -40,17 +40,17 @@ import retrofit2.Callback import retrofit2.Response class ItemCardAdapter( - private val app: Activity, - private val items: ArrayList, - private val api: SelfossApi, + override val app: Activity, + override var items: ArrayList, + override val api: SelfossApi, private val helper: CustomTabActivityHelper, private val internalBrowser: Boolean, private val articleViewer: Boolean, private val fullHeightCards: Boolean, private val appColors: AppColors, - val debugReadingItems: Boolean, - val userIdentifier: String -) : RecyclerView.Adapter() { + override val debugReadingItems: Boolean, + override val userIdentifier: String +) : ItemsAdapter() { private val c: Context = app.baseContext private val generator: ColorGenerator = ColorGenerator.MATERIAL private val imageMaxHeight: Int = c.resources.getDimension(R.dimen.card_image_max_height).toInt() @@ -103,86 +103,6 @@ class ItemCardAdapter( return items.size } - private fun doUnmark(i: Item, position: Int) { - val s = Snackbar - .make( - app.findViewById(R.id.coordLayout), - R.string.marked_as_read, - Snackbar.LENGTH_LONG - ) - .setAction(R.string.undo_string) { - items.add(position, i) - notifyItemInserted(position) - - api.unmarkItem(i.id).enqueue(object : Callback { - override fun onResponse( - call: Call, - response: Response - ) { - } - - override fun onFailure(call: Call, t: Throwable) { - items.remove(i) - notifyItemRemoved(position) - doUnmark(i, position) - } - }) - } - - val view = s.view - val tv: TextView = view.findViewById(android.support.design.R.id.snackbar_text) - tv.setTextColor(Color.WHITE) - s.show() - } - - fun removeItemAtIndex(position: Int) { - - val i = items[position] - - items.remove(i) - notifyItemRemoved(position) - - api.markItem(i.id).enqueue(object : Callback { - override fun onResponse( - call: Call, - response: Response - ) { - if (!response.succeeded() && debugReadingItems) { - val message = - "message: ${response.message()} " + - "response isSuccess: ${response.isSuccessful} " + - "response code: ${response.code()} " + - "response message: ${response.message()} " + - "response errorBody: ${response.errorBody()?.string()} " + - "body success: ${response.body()?.success} " + - "body isSuccess: ${response.body()?.isSuccess}" - Crashlytics.setUserIdentifier(userIdentifier) - Crashlytics.log(100, "READ_DEBUG_SUCCESS", message) - Crashlytics.logException(Exception("Was success, but did it work ?")) - - Toast.makeText(c, message, Toast.LENGTH_LONG).show() - } - doUnmark(i, position) - } - - override fun onFailure(call: Call, t: Throwable) { - if (debugReadingItems) { - Crashlytics.setUserIdentifier(userIdentifier) - Crashlytics.log(100, "READ_DEBUG_ERROR", t.message) - Crashlytics.logException(t) - Toast.makeText(c, t.message, Toast.LENGTH_LONG).show() - } - Toast.makeText( - app, - app.getString(R.string.cant_mark_read), - Toast.LENGTH_SHORT - ).show() - items.add(i) - notifyItemInserted(position) - } - }) - } - inner class ViewHolder(val mView: CardView) : RecyclerView.ViewHolder(mView) { init { mView.setCardBackgroundColor(appColors.cardBackground) diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/adapters/ItemListAdapter.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/adapters/ItemListAdapter.kt index 8aff5b6..7ae1b34 100644 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/adapters/ItemListAdapter.kt +++ b/app/src/main/java/apps/amine/bou/readerforselfoss/adapters/ItemListAdapter.kt @@ -39,16 +39,16 @@ import java.util.* import kotlin.collections.ArrayList class ItemListAdapter( - private val app: Activity, - private val items: ArrayList, - private val api: SelfossApi, + override val app: Activity, + override var items: ArrayList, + override val api: SelfossApi, private val helper: CustomTabActivityHelper, private val clickBehavior: Boolean, private val internalBrowser: Boolean, private val articleViewer: Boolean, - val debugReadingItems: Boolean, - val userIdentifier: String -) : RecyclerView.Adapter() { + override val debugReadingItems: Boolean, + override val userIdentifier: String +) : ItemsAdapter() { private val generator: ColorGenerator = ColorGenerator.MATERIAL private val c: Context = app.baseContext private val bars: ArrayList = ArrayList(Collections.nCopies(items.size + 1, false)) @@ -119,84 +119,7 @@ class ItemListAdapter( override fun getItemCount(): Int = items.size - private fun doUnmark(i: Item, position: Int) { - val s = Snackbar - .make( - app.findViewById(R.id.coordLayout), - R.string.marked_as_read, - Snackbar.LENGTH_LONG - ) - .setAction(R.string.undo_string) { - items.add(position, i) - notifyItemInserted(position) - api.unmarkItem(i.id).enqueue(object : Callback { - override fun onResponse( - call: Call, - response: Response - ) { - } - - override fun onFailure(call: Call, t: Throwable) { - items.remove(i) - notifyItemRemoved(position) - doUnmark(i, position) - } - }) - } - - val view = s.view - val tv: TextView = view.findViewById(android.support.design.R.id.snackbar_text) - tv.setTextColor(Color.WHITE) - s.show() - } - - fun removeItemAtIndex(position: Int) { - - val i = items[position] - - items.remove(i) - notifyItemRemoved(position) - - api.markItem(i.id).enqueue(object : Callback { - override fun onResponse( - call: Call, - response: Response - ) { - if (!response.succeeded() && debugReadingItems) { - val message = - "message: ${response.message()} " + - "response isSuccess: ${response.isSuccessful} " + - "response code: ${response.code()} " + - "response message: ${response.message()} " + - "response errorBody: ${response.errorBody()?.string()} " + - "body success: ${response.body()?.success} " + - "body isSuccess: ${response.body()?.isSuccess}" - Crashlytics.setUserIdentifier(userIdentifier) - Crashlytics.log(100, "READ_DEBUG_SUCCESS", message) - Crashlytics.logException(Exception("Was success, but did it work ?")) - Toast.makeText(c, message, Toast.LENGTH_LONG).show() - } - doUnmark(i, position) - } - - override fun onFailure(call: Call, t: Throwable) { - if (debugReadingItems) { - Crashlytics.setUserIdentifier(userIdentifier) - Crashlytics.log(100, "READ_DEBUG_ERROR", t.message) - Crashlytics.logException(t) - Toast.makeText(c, t.message, Toast.LENGTH_LONG).show() - } - Toast.makeText( - app, - app.getString(R.string.cant_mark_read), - Toast.LENGTH_SHORT - ).show() - items.add(i) - notifyItemInserted(position) - } - }) - } inner class ViewHolder(val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) { @@ -312,4 +235,6 @@ class ItemListAdapter( } } } + + } diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/adapters/ItemsAdapter.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/adapters/ItemsAdapter.kt new file mode 100644 index 0000000..565a02c --- /dev/null +++ b/app/src/main/java/apps/amine/bou/readerforselfoss/adapters/ItemsAdapter.kt @@ -0,0 +1,121 @@ +package apps.amine.bou.readerforselfoss.adapters + +import android.app.Activity +import android.graphics.Color +import android.support.design.widget.Snackbar +import android.support.v7.widget.RecyclerView +import android.widget.TextView +import android.widget.Toast +import apps.amine.bou.readerforselfoss.R +import apps.amine.bou.readerforselfoss.api.selfoss.Item +import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi +import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse +import apps.amine.bou.readerforselfoss.utils.succeeded +import com.crashlytics.android.Crashlytics +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response + +abstract class ItemsAdapter : RecyclerView.Adapter() { + abstract var items: ArrayList + abstract val api: SelfossApi + abstract val debugReadingItems: Boolean + abstract val userIdentifier: String + abstract val app: Activity + + fun updateAllItems(newItems: ArrayList) { + items = newItems + notifyDataSetChanged() + } + + private fun doUnmark(i: Item, position: Int) { + val s = Snackbar + .make( + app.findViewById(R.id.coordLayout), + R.string.marked_as_read, + Snackbar.LENGTH_LONG + ) + .setAction(R.string.undo_string) { + items.add(position, i) + notifyItemInserted(position) + + api.unmarkItem(i.id).enqueue(object : Callback { + override fun onResponse( + call: Call, + response: Response + ) { + } + + override fun onFailure(call: Call, t: Throwable) { + items.remove(i) + notifyItemRemoved(position) + doUnmark(i, position) + } + }) + } + + val view = s.view + val tv: TextView = view.findViewById(android.support.design.R.id.snackbar_text) + tv.setTextColor(Color.WHITE) + s.show() + } + + fun removeItemAtIndex(position: Int) { + + val i = items[position] + + items.remove(i) + notifyItemRemoved(position) + + api.markItem(i.id).enqueue(object : Callback { + override fun onResponse( + call: Call, + response: Response + ) { + if (!response.succeeded() && debugReadingItems) { + val message = + "message: ${response.message()} " + + "response isSuccess: ${response.isSuccessful} " + + "response code: ${response.code()} " + + "response message: ${response.message()} " + + "response errorBody: ${response.errorBody()?.string()} " + + "body success: ${response.body()?.success} " + + "body isSuccess: ${response.body()?.isSuccess}" + Crashlytics.setUserIdentifier(userIdentifier) + Crashlytics.log(100, "READ_DEBUG_SUCCESS", message) + Crashlytics.logException(Exception("Was success, but did it work ?")) + + Toast.makeText(app.baseContext, message, Toast.LENGTH_LONG).show() + } + doUnmark(i, position) + } + + override fun onFailure(call: Call, t: Throwable) { + if (debugReadingItems) { + Crashlytics.setUserIdentifier(userIdentifier) + Crashlytics.log(100, "READ_DEBUG_ERROR", t.message) + Crashlytics.logException(t) + Toast.makeText(app.baseContext, t.message, Toast.LENGTH_LONG).show() + } + Toast.makeText( + app, + app.getString(R.string.cant_mark_read), + Toast.LENGTH_SHORT + ).show() + items.add(i) + notifyItemInserted(position) + } + }) + } + + fun addItemAtIndex(item: Item, position: Int) { + items.add(position, item) + notifyItemInserted(position) + } + + fun addItemsAtEnd(newItems: List) { + val oldSize = items.size + items.addAll(newItems) + notifyItemRangeInserted(oldSize, newItems.size) + } +} \ No newline at end of file diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/api/mercury/MercuryModels.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/api/mercury/MercuryModels.kt index 58ed435..65bb722 100644 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/api/mercury/MercuryModels.kt +++ b/app/src/main/java/apps/amine/bou/readerforselfoss/api/mercury/MercuryModels.kt @@ -6,9 +6,9 @@ import com.google.gson.annotations.SerializedName class ParsedContent( @SerializedName("title") val title: String, - @SerializedName("content") val content: String, + @SerializedName("content") val content: String?, @SerializedName("date_published") val date_published: String, - @SerializedName("lead_image_url") val lead_image_url: String, + @SerializedName("lead_image_url") val lead_image_url: String?, @SerializedName("dek") val dek: String, @SerializedName("url") val url: String, @SerializedName("domain") val domain: String, diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/fragments/ArticleFragment.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/fragments/ArticleFragment.kt index 67124fa..750a796 100644 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/fragments/ArticleFragment.kt +++ b/app/src/main/java/apps/amine/bou/readerforselfoss/fragments/ArticleFragment.kt @@ -164,14 +164,14 @@ class ArticleFragment : Fragment() { response: Response ) { try { - if (response.body() != null && response.body()!!.content != null && response.body()!!.content.isNotEmpty()) { + if (response.body() != null && response.body()!!.content != null && !response.body()!!.content.isNullOrEmpty()) { rootView.source.text = response.body()!!.domain rootView.titleView.text = response.body()!!.title url = response.body()!!.url - htmlToWebview(response.body()!!.content, prefs) + htmlToWebview(response.body()!!.content.orEmpty(), prefs) - if (response.body()!!.lead_image_url != null && !response.body()!!.lead_image_url.isEmpty()) { + if (response.body()!!.lead_image_url != null && !response.body()!!.lead_image_url.isNullOrEmpty()) { rootView.imageView.visibility = View.VISIBLE Glide .with(activity!!.baseContext)