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.
- Better handling for articles update. (See #169)
**1.5.5.x (didn't last long) AND 1.5.6.x**
- 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.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<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() != items) {
if (appendResults) {
items.addAll(response.body() as ArrayList<Item>)
} else {
items = response.body() as ArrayList<Item>
}
if (shouldUpdate) {
items = response.body() as ArrayList<Item>
}
} 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()

View File

@ -40,17 +40,17 @@ import retrofit2.Callback
import retrofit2.Response
class ItemCardAdapter(
private val app: Activity,
private val items: ArrayList<Item>,
private val api: SelfossApi,
override val app: Activity,
override var items: ArrayList<Item>,
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<ItemCardAdapter.ViewHolder>() {
override val debugReadingItems: Boolean,
override val userIdentifier: String
) : ItemsAdapter<ItemCardAdapter.ViewHolder>() {
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<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) {
init {
mView.setCardBackgroundColor(appColors.cardBackground)

View File

@ -39,16 +39,16 @@ import java.util.*
import kotlin.collections.ArrayList
class ItemListAdapter(
private val app: Activity,
private val items: ArrayList<Item>,
private val api: SelfossApi,
override val app: Activity,
override var items: ArrayList<Item>,
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<ItemListAdapter.ViewHolder>() {
override val debugReadingItems: Boolean,
override val userIdentifier: String
) : ItemsAdapter<ItemListAdapter.ViewHolder>() {
private val generator: ColorGenerator = ColorGenerator.MATERIAL
private val c: Context = app.baseContext
private val bars: ArrayList<Boolean> = 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<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) {
@ -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(
@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,

View File

@ -164,14 +164,14 @@ class ArticleFragment : Fragment() {
response: Response<ParsedContent>
) {
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)