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.
This commit is contained in:
Amine Bou 2018-01-06 15:33:58 +01:00
parent 80ad65b196
commit 2c8902d404
7 changed files with 256 additions and 262 deletions

View File

@ -18,6 +18,8 @@
- Updated the Contribution guide about translations. - 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** **1.5.5.x (didn't last long) AND 1.5.6.x**
- Toolbar in reader activity. - Toolbar in reader activity.

View File

@ -17,13 +17,13 @@ import android.support.v7.widget.RecyclerView
import android.support.v7.widget.SearchView import android.support.v7.widget.SearchView
import android.support.v7.widget.StaggeredGridLayoutManager import android.support.v7.widget.StaggeredGridLayoutManager
import android.support.v7.widget.helper.ItemTouchHelper import android.support.v7.widget.helper.ItemTouchHelper
import android.util.Log
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.Toast import android.widget.Toast
import apps.amine.bou.readerforselfoss.adapters.ItemCardAdapter import apps.amine.bou.readerforselfoss.adapters.ItemCardAdapter
import apps.amine.bou.readerforselfoss.adapters.ItemListAdapter 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.Item
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
import apps.amine.bou.readerforselfoss.api.selfoss.Sources import apps.amine.bou.readerforselfoss.api.selfoss.Sources
@ -119,6 +119,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
private var recyclerViewScrollListener: RecyclerView.OnScrollListener? = null private var recyclerViewScrollListener: RecyclerView.OnScrollListener? = null
private lateinit var settings: SharedPreferences private lateinit var settings: SharedPreferences
private var recyclerAdapter: RecyclerView.Adapter<*>? = null
private var badgeNew: Int = -1 private var badgeNew: Int = -1
private var badgeAll: Int = -1 private var badgeAll: Int = -1
private var badgeFavs: Int = -1 private var badgeFavs: Int = -1
@ -161,7 +163,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
handleBottomBar() handleBottomBar()
handleDrawer() handleDrawer()
reloadLayoutManager()
handleSwipeRefreshLayout() handleSwipeRefreshLayout()
} }
@ -314,6 +315,16 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
handleSharedPrefs() handleSharedPrefs()
reloadLayoutManager()
if (!infiniteScroll) {
recyclerView.setHasFixedSize(true)
} else {
handleInfiniteScroll()
}
handleBottomBarActions()
getElementsAccordingToTab() getElementsAccordingToTab()
} }
@ -623,48 +634,67 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
} }
private fun reloadLayoutManager() { private fun reloadLayoutManager() {
val mLayoutManager: RecyclerView.LayoutManager val currentManager = recyclerView.layoutManager
if (shouldBeCardView) { val layoutManager: RecyclerView.LayoutManager
mLayoutManager = StaggeredGridLayoutManager(
calculateNoOfColumns(), // This will only update the layout manager if settings changed
StaggeredGridLayoutManager.VERTICAL when (currentManager) {
) is StaggeredGridLayoutManager ->
mLayoutManager.gapStrategy = StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS if (!shouldBeCardView) {
} else { layoutManager = GridLayoutManager(this, calculateNoOfColumns())
mLayoutManager = 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 { bottomBar.setTabSelectedListener(object : BottomNavigationBar.OnTabSelectedListener {
override fun onTabUnselected(position: Int) = Unit override fun onTabUnselected(position: Int) = Unit
override fun onTabReselected(position: Int) = override fun onTabReselected(position: Int) {
when (mLayoutManager) { val layoutManager = recyclerView.adapter
is StaggeredGridLayoutManager ->
if (mLayoutManager.findFirstCompletelyVisibleItemPositions(null)[0] == 0) { when (layoutManager) {
getElementsAccordingToTab() is StaggeredGridLayoutManager ->
} else { if (layoutManager.findFirstCompletelyVisibleItemPositions(null)[0] == 0) {
mLayoutManager.scrollToPositionWithOffset(0, 0) getElementsAccordingToTab()
} } else {
is GridLayoutManager -> layoutManager.scrollToPositionWithOffset(0, 0)
if (mLayoutManager.findFirstCompletelyVisibleItemPosition() == 0) { }
getElementsAccordingToTab() is GridLayoutManager ->
} else { if (layoutManager.findFirstCompletelyVisibleItemPosition() == 0) {
mLayoutManager.scrollToPositionWithOffset(0, 0) getElementsAccordingToTab()
} } else {
else -> Unit layoutManager.scrollToPositionWithOffset(0, 0)
} }
else -> Unit
}
}
override fun onTabSelected(position: Int) { override fun onTabSelected(position: Int) {
offset = 0 offset = 0
@ -739,25 +769,21 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
call: (String?, Long?, String?) -> Call<List<Item>> call: (String?, Long?, String?) -> Call<List<Item>>
) { ) {
fun handleItemsResponse(response: Response<List<Item>>) { fun handleItemsResponse(response: Response<List<Item>>) {
val didUpdate = (response.body() != items) val shouldUpdate = (response.body() != items)
if (response.body() != null) { if (response.body() != null) {
if (response.body() != items) { if (shouldUpdate) {
if (appendResults) { items = response.body() as ArrayList<Item>
items.addAll(response.body() as ArrayList<Item>)
} else {
items = response.body() as ArrayList<Item>
}
} }
} else { } else {
if (!appendResults) { if (!appendResults) {
items = ArrayList() items = ArrayList()
} }
} }
if (didUpdate) { if (shouldUpdate) {
handleListResult(appendResults) handleListResult(appendResults)
} }
mayBeEmpty() if (!appendResults) mayBeEmpty()
swipeRefreshLayout.isRefreshing = false 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<*> recyclerView.addItemDecoration(
if (shouldBeCardView) { DividerItemDecoration(
mAdapter = this@HomeActivity,
ItemCardAdapter( DividerItemDecoration.VERTICAL
this, )
items, )
api, }
customTabActivityHelper, recyclerView.adapter = recyclerAdapter
internalBrowser,
articleViewer,
fullHeightCards,
appColors,
debugReadingItems,
userIdentifier
)
} else { } else {
mAdapter = if (!appendResults) {
ItemListAdapter( (recyclerAdapter as ItemsAdapter<*>).updateAllItems(items)
this, } else {
items, (recyclerAdapter as ItemsAdapter<*>).addItemsAtEnd(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!!)
} }
reloadBadges() reloadBadges()

View File

@ -40,17 +40,17 @@ import retrofit2.Callback
import retrofit2.Response import retrofit2.Response
class ItemCardAdapter( class ItemCardAdapter(
private val app: Activity, override val app: Activity,
private val items: ArrayList<Item>, override var items: ArrayList<Item>,
private val api: SelfossApi, override val api: SelfossApi,
private val helper: CustomTabActivityHelper, private val helper: CustomTabActivityHelper,
private val internalBrowser: Boolean, private val internalBrowser: Boolean,
private val articleViewer: Boolean, private val articleViewer: Boolean,
private val fullHeightCards: Boolean, private val fullHeightCards: Boolean,
private val appColors: AppColors, private val appColors: AppColors,
val debugReadingItems: Boolean, override val debugReadingItems: Boolean,
val userIdentifier: String override val userIdentifier: String
) : RecyclerView.Adapter<ItemCardAdapter.ViewHolder>() { ) : ItemsAdapter<ItemCardAdapter.ViewHolder>() {
private val c: Context = app.baseContext private val c: Context = app.baseContext
private val generator: ColorGenerator = ColorGenerator.MATERIAL private val generator: ColorGenerator = ColorGenerator.MATERIAL
private val imageMaxHeight: Int = c.resources.getDimension(R.dimen.card_image_max_height).toInt() private val imageMaxHeight: Int = c.resources.getDimension(R.dimen.card_image_max_height).toInt()
@ -103,86 +103,6 @@ class ItemCardAdapter(
return items.size 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<SuccessResponse> {
override fun onResponse(
call: Call<SuccessResponse>,
response: Response<SuccessResponse>
) {
}
override fun onFailure(call: Call<SuccessResponse>, 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<SuccessResponse> {
override fun onResponse(
call: Call<SuccessResponse>,
response: Response<SuccessResponse>
) {
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<SuccessResponse>, 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) { inner class ViewHolder(val mView: CardView) : RecyclerView.ViewHolder(mView) {
init { init {
mView.setCardBackgroundColor(appColors.cardBackground) mView.setCardBackgroundColor(appColors.cardBackground)

View File

@ -39,16 +39,16 @@ import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
class ItemListAdapter( class ItemListAdapter(
private val app: Activity, override val app: Activity,
private val items: ArrayList<Item>, override var items: ArrayList<Item>,
private val api: SelfossApi, override val api: SelfossApi,
private val helper: CustomTabActivityHelper, private val helper: CustomTabActivityHelper,
private val clickBehavior: Boolean, private val clickBehavior: Boolean,
private val internalBrowser: Boolean, private val internalBrowser: Boolean,
private val articleViewer: Boolean, private val articleViewer: Boolean,
val debugReadingItems: Boolean, override val debugReadingItems: Boolean,
val userIdentifier: String override val userIdentifier: String
) : RecyclerView.Adapter<ItemListAdapter.ViewHolder>() { ) : ItemsAdapter<ItemListAdapter.ViewHolder>() {
private val generator: ColorGenerator = ColorGenerator.MATERIAL private val generator: ColorGenerator = ColorGenerator.MATERIAL
private val c: Context = app.baseContext private val c: Context = app.baseContext
private val bars: ArrayList<Boolean> = ArrayList(Collections.nCopies(items.size + 1, false)) private val bars: ArrayList<Boolean> = ArrayList(Collections.nCopies(items.size + 1, false))
@ -119,84 +119,7 @@ class ItemListAdapter(
override fun getItemCount(): Int = items.size 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<SuccessResponse> {
override fun onResponse(
call: Call<SuccessResponse>,
response: Response<SuccessResponse>
) {
}
override fun onFailure(call: Call<SuccessResponse>, 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<SuccessResponse> {
override fun onResponse(
call: Call<SuccessResponse>,
response: Response<SuccessResponse>
) {
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<SuccessResponse>, 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) { inner class ViewHolder(val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) {
@ -312,4 +235,6 @@ class ItemListAdapter(
} }
} }
} }
} }

View File

@ -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<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapter<VH>() {
abstract var items: ArrayList<Item>
abstract val api: SelfossApi
abstract val debugReadingItems: Boolean
abstract val userIdentifier: String
abstract val app: Activity
fun updateAllItems(newItems: ArrayList<Item>) {
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<SuccessResponse> {
override fun onResponse(
call: Call<SuccessResponse>,
response: Response<SuccessResponse>
) {
}
override fun onFailure(call: Call<SuccessResponse>, 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<SuccessResponse> {
override fun onResponse(
call: Call<SuccessResponse>,
response: Response<SuccessResponse>
) {
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<SuccessResponse>, 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<Item>) {
val oldSize = items.size
items.addAll(newItems)
notifyItemRangeInserted(oldSize, newItems.size)
}
}

View File

@ -6,9 +6,9 @@ import com.google.gson.annotations.SerializedName
class ParsedContent( class ParsedContent(
@SerializedName("title") val title: String, @SerializedName("title") val title: String,
@SerializedName("content") val content: String, @SerializedName("content") val content: String?,
@SerializedName("date_published") val date_published: 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("dek") val dek: String,
@SerializedName("url") val url: String, @SerializedName("url") val url: String,
@SerializedName("domain") val domain: String, @SerializedName("domain") val domain: String,

View File

@ -164,14 +164,14 @@ class ArticleFragment : Fragment() {
response: Response<ParsedContent> response: Response<ParsedContent>
) { ) {
try { 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.source.text = response.body()!!.domain
rootView.titleView.text = response.body()!!.title rootView.titleView.text = response.body()!!.title
url = response.body()!!.url 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 rootView.imageView.visibility = View.VISIBLE
Glide Glide
.with(activity!!.baseContext) .with(activity!!.baseContext)