More debugging. Source adding is still broken. Needs DB integration.

This commit is contained in:
aminecmi 2022-05-21 22:05:11 +02:00
parent 6e38e8753c
commit 4c69e72499
18 changed files with 247 additions and 327 deletions

View File

@ -133,7 +133,7 @@ class AddSourceActivity : AppCompatActivity() {
} }
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.Main).launch {
var items = api!!.spouts() var items = api!!.spouts()
if (items != null) { if (items != null) {
@ -192,7 +192,7 @@ class AddSourceActivity : AppCompatActivity() {
Toast.makeText(this, R.string.form_not_complete, Toast.LENGTH_SHORT).show() Toast.makeText(this, R.string.form_not_complete, Toast.LENGTH_SHORT).show()
} }
else -> { else -> {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.Main).launch {
val response: SelfossModel.SuccessResponse? = api.createSourceForVersion( val response: SelfossModel.SuccessResponse? = api.createSourceForVersion(
title, title,
url, url,

View File

@ -235,7 +235,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
lastFetchDone = false lastFetchDone = false
handleDrawerItems() handleDrawerItems()
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
service.refreshFocusedItems(itemsNumber, applicationContext.isNetworkAvailable())
getElementsAccordingToTab() getElementsAccordingToTab()
binding.swipeRefreshLayout.isRefreshing = false binding.swipeRefreshLayout.isRefreshing = false
} }
@ -276,7 +275,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
reloadBadgeContent() reloadBadgeContent()
val tagHashes = i.tags.split(",").map { it.longHash() } val tagHashes = i.tags.map { it.longHash() }
tagsBadge = tagsBadge.map { tagsBadge = tagsBadge.map {
if (tagHashes.contains(it.key)) { if (tagHashes.contains(it.key)) {
(it.key to (it.value - 1)) (it.key to (it.value - 1))
@ -868,9 +867,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
override fun onTabUnselected(position: Int) = Unit override fun onTabUnselected(position: Int) = Unit
override fun onTabReselected(position: Int) { override fun onTabReselected(position: Int) {
val layoutManager = binding.recyclerView.adapter
when (layoutManager) { when (val layoutManager = binding.recyclerView.adapter) {
is StaggeredGridLayoutManager -> is StaggeredGridLayoutManager ->
if (layoutManager.findFirstCompletelyVisibleItemPositions(null)[0] == 0) { if (layoutManager.findFirstCompletelyVisibleItemPositions(null)[0] == 0) {
getElementsAccordingToTab() getElementsAccordingToTab()
@ -902,10 +900,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
private fun fetchOnEmptyList() { private fun fetchOnEmptyList() {
binding.recyclerView.doOnNextLayout { binding.recyclerView.doOnNextLayout {
// Todo: // TODO: do if last element (or is empty ?)
// if (SharedItems.focusedItems.size - 1 == getLastVisibleItem()) { getElementsAccordingToTab(true)
// getElementsAccordingToTab(true)
// }
} }
} }
@ -927,8 +923,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
} }
private fun getLastVisibleItem() : Int { private fun getLastVisibleItem() : Int {
val manager = binding.recyclerView.layoutManager return when (val manager = binding.recyclerView.layoutManager) {
return when (manager) {
is StaggeredGridLayoutManager -> manager.findLastCompletelyVisibleItemPositions( is StaggeredGridLayoutManager -> manager.findLastCompletelyVisibleItemPositions(
null null
).last() ).last()
@ -945,8 +940,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
} }
private fun getElementsAccordingToTab( private fun getElementsAccordingToTab(
appendResults: Boolean = false, appendResults: Boolean = false
offsetOverride: Int? = null
) { ) {
fun doGetAccordingToTab() { fun doGetAccordingToTab() {
when (elementsShown) { when (elementsShown) {
@ -957,12 +951,11 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
} }
} }
// Todo: offset = if (appendResults && items.size > 0) {
// offset = if (appendResults) { items.size - 1
// SharedItems.focusedItems.size - 1 } else {
// } else { 0
// 0 }
// }
firstVisible = if (appendResults) firstVisible else 0 firstVisible = if (appendResults) firstVisible else 0
doGetAccordingToTab() doGetAccordingToTab()
@ -970,41 +963,42 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
private fun getUnRead(appendResults: Boolean = false) { private fun getUnRead(appendResults: Boolean = false) {
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
// Todo: binding.swipeRefreshLayout.isRefreshing = true
// if (appendResults || !SharedItems.fetchedUnread) { val apiItems = service.getUnreadItems(itemsNumber, offset, applicationContext.isNetworkAvailable())
// binding.swipeRefreshLayout.isRefreshing = true if (appendResults) {
// service.getUnreadItems(itemsNumber, offset, applicationContext.isNetworkAvailable()) apiItems?.let { items.addAll(it) }
// binding.swipeRefreshLayout.isRefreshing = false } else {
// } items = apiItems.orEmpty() as ArrayList<SelfossModel.Item>
// Todo: SharedItems.getUnRead() }
// Todo: items = SharedItems.focusedItems binding.swipeRefreshLayout.isRefreshing = false
handleListResult() handleListResult()
} }
} }
private fun getRead(appendResults: Boolean = false) { private fun getRead(appendResults: Boolean = false) {
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
// Todo: binding.swipeRefreshLayout.isRefreshing = true
// if (appendResults || !SharedItems.fetchedAll) { val apiItems = service.getReadItems(itemsNumber, offset, applicationContext.isNetworkAvailable())
// binding.swipeRefreshLayout.isRefreshing = true if (appendResults) {
// service.getReadItems(itemsNumber, offset, applicationContext.isNetworkAvailable()) apiItems?.let { items.addAll(it) }
// binding.swipeRefreshLayout.isRefreshing = false } else {
// } items = apiItems.orEmpty() as ArrayList<SelfossModel.Item>
// SharedItems.getAll() }
// items = SharedItems.focusedItems binding.swipeRefreshLayout.isRefreshing = false
handleListResult() handleListResult()
} }
} }
private fun getStarred(appendResults: Boolean = false) { private fun getStarred(appendResults: Boolean = false) {
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
if (appendResults || !searchService.fetchedStarred) { binding.swipeRefreshLayout.isRefreshing = true
binding.swipeRefreshLayout.isRefreshing = true val apiItems = service.getStarredItems(itemsNumber, offset, applicationContext.isNetworkAvailable())
service.getStarredItems(itemsNumber, offset, applicationContext.isNetworkAvailable()) if (appendResults) {
binding.swipeRefreshLayout.isRefreshing = false apiItems?.let { items.addAll(it) }
} else {
items = apiItems.orEmpty() as ArrayList<SelfossModel.Item>
} }
// Todo: SharedItems.getStarred() binding.swipeRefreshLayout.isRefreshing = false
// Todo: items = SharedItems.focusedItems
handleListResult() handleListResult()
} }
} }
@ -1069,7 +1063,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
} }
binding.recyclerView.adapter = recyclerAdapter binding.recyclerView.adapter = recyclerAdapter
} else { } else {
(recyclerAdapter as ItemsAdapter<*>).updateAllItems() (recyclerAdapter as ItemsAdapter<*>).updateAllItems(items)
} }
reloadBadges() reloadBadges()
@ -1186,7 +1180,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
if (this@HomeActivity.isNetworkAvailable(null, offlineShortcut)) { if (this@HomeActivity.isNetworkAvailable(null, offlineShortcut)) {
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
val success = service.readAll(applicationContext.isNetworkAvailable()) val success = service.readAll(items.map { it.id.toString() }, applicationContext.isNetworkAvailable())
if (success) { if (success) {
Toast.makeText( Toast.makeText(
this@HomeActivity, this@HomeActivity,

View File

@ -130,7 +130,8 @@ class ReaderActivity : AppCompatActivity() {
private fun readItem(item: SelfossModel.Item) { private fun readItem(item: SelfossModel.Item) {
if (markOnScroll) { if (markOnScroll) {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
// Todo: SharedItems.readItem(applicationContext, api, db, item) api.markAsRead(item.id.toString())
// TODO: update item in DB
} }
} }
} }
@ -177,7 +178,7 @@ class ReaderActivity : AppCompatActivity() {
inflater.inflate(R.menu.reader_menu, menu) inflater.inflate(R.menu.reader_menu, menu)
toolbarMenu = menu toolbarMenu = menu
if (allItems.isNotEmpty() && allItems[currentItem].starred == 1) { if (allItems.isNotEmpty() && allItems[currentItem].starred) {
canRemoveFromFavorite() canRemoveFromFavorite()
} else { } else {
canFavorite() canFavorite()
@ -194,7 +195,7 @@ class ReaderActivity : AppCompatActivity() {
override fun onPageSelected(position: Int) { override fun onPageSelected(position: Int) {
super.onPageSelected(position) super.onPageSelected(position)
if (allItems[position].starred == 1) { if (allItems[position].starred) {
canRemoveFromFavorite() canRemoveFromFavorite()
} else { } else {
canFavorite() canFavorite()
@ -225,26 +226,16 @@ class ReaderActivity : AppCompatActivity() {
return true return true
} }
R.id.star -> { R.id.star -> {
if (allItems[binding.pager.currentItem].starred == 1) { if (allItems[binding.pager.currentItem].starred) {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
// Todo: api.unstarr(allItems[binding.pager.currentItem].id.toString())
// SharedItems.unstarItem( // TODO: update in DB
// this@ReaderActivity,
// api,
// db,
// allItems[binding.pager.currentItem]
// )
} }
afterUnsave() afterUnsave()
} else { } else {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
// Todo: api.starr(allItems[binding.pager.currentItem].id.toString())
// SharedItems.starItem( // TODO: update in DB
// this@ReaderActivity,
// api,
// db,
// allItems[binding.pager.currentItem]
// )
} }
afterSave() afterSave()
} }

View File

@ -80,7 +80,7 @@ class SourcesActivity : AppCompatActivity() {
binding.recyclerView.layoutManager = mLayoutManager binding.recyclerView.layoutManager = mLayoutManager
if (this@SourcesActivity.isNetworkAvailable(binding.recyclerView)) { if (this@SourcesActivity.isNetworkAvailable(binding.recyclerView)) {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.Main).launch {
val response = api.sources() val response = api.sources()
if (response != null) { if (response != null) {
items = response items = response

View File

@ -59,7 +59,7 @@ class ItemCardAdapter(
with(holder) { with(holder) {
val itm = items[position] val itm = items[position]
binding.favButton.isSelected = itm.starred == 1 binding.favButton.isSelected = itm.starred
binding.title.text = itm.getTitleDecoded() binding.title.text = itm.getTitleDecoded()
binding.title.setOnTouchListener(LinkOnTouchListener()) binding.title.setOnTouchListener(LinkOnTouchListener())
@ -112,17 +112,19 @@ class ItemCardAdapter(
binding.favButton.setOnClickListener { binding.favButton.setOnClickListener {
val item = items[bindingAdapterPosition] val item = items[bindingAdapterPosition]
if (c.isNetworkAvailable()) { if (c.isNetworkAvailable()) {
if (item.starred == 1) { if (item.starred) {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
// Todo: SharedItems.unstarItem(c, api, db, item) api.unstarr(item.id.toString())
// TODO: save to db
} }
item.starred = 0 item.starred = false
binding.favButton.isSelected = false binding.favButton.isSelected = false
} else { } else {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
// Todo: SharedItems.starItem(c, api, db, item) api.starr(item.id.toString())
// TODO: save to db
} }
item.starred = 1 item.starred = true
binding.favButton.isSelected = true binding.favButton.isSelected = true
} }
} }

View File

@ -29,10 +29,10 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
abstract val searchService: SearchService abstract val searchService: SearchService
abstract val updateItems: (ArrayList<SelfossModel.Item>) -> Unit abstract val updateItems: (ArrayList<SelfossModel.Item>) -> Unit
fun updateAllItems() { fun updateAllItems(items: ArrayList<SelfossModel.Item>) {
items = ArrayList() // TODO: SharedItems.focusedItems this.items = items
notifyDataSetChanged() notifyDataSetChanged()
updateItems(items) updateItems(this.items)
} }
private fun unmarkSnackbar(i: SelfossModel.Item, position: Int) { private fun unmarkSnackbar(i: SelfossModel.Item, position: Int) {
@ -44,14 +44,8 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
) )
.setAction(R.string.undo_string) { .setAction(R.string.undo_string) {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
// Todo: SharedItems.unreadItem(app, api, db, i) unreadItemAtIndex(position, false)
} }
// Todo:
// if (SharedItems.displayedItems == "unread") {
// addItemAtIndex(i, position)
// } else {
// notifyItemChanged(position)
// }
} }
val view = s.view val view = s.view
@ -68,17 +62,7 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
Snackbar.LENGTH_LONG Snackbar.LENGTH_LONG
) )
.setAction(R.string.undo_string) { .setAction(R.string.undo_string) {
CoroutineScope(Dispatchers.IO).launch { readItemAtIndex(position)
// Todo: SharedItems.readItem(app, api, db, items[position])
}
// Todo: items = SharedItems.focusedItems
// Todo:
// if (SharedItems.displayedItems == "unread") {
// notifyItemRemoved(position)
// updateItems(items)
// } else {
// notifyItemChanged(position)
// }
} }
val view = s.view val view = s.view
@ -88,18 +72,19 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
} }
fun handleItemAtIndex(position: Int) { fun handleItemAtIndex(position: Int) {
// Todo: if (items[position].unread) {
// if (SharedItems.unreadItemStatusAtIndex(position)) { readItemAtIndex(position)
// readItemAtIndex(position) } else {
// } else { unreadItemAtIndex(position)
// unreadItemAtIndex(position) }
// }
} }
private fun readItemAtIndex(position: Int) { private fun readItemAtIndex(position: Int, showSnackbar: Boolean = true) {
val i = items[position] val i = items[position]
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
// Todo: SharedItems.readItem(app, api, db, i) api.markAsRead(i.id.toString())
// TODO: update db
} }
// Todo: // Todo:
// if (SharedItems.displayedItems == "unread") { // if (SharedItems.displayedItems == "unread") {
@ -109,15 +94,22 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
// } else { // } else {
// notifyItemChanged(position) // notifyItemChanged(position)
// } // }
unmarkSnackbar(i, position) if (showSnackbar) {
unmarkSnackbar(i, position)
}
} }
private fun unreadItemAtIndex(position: Int) { private fun unreadItemAtIndex(position: Int, showSnackbar: Boolean = true) {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
api.unmarkAsRead(items[position].id.toString())
// Todo: SharedItems.unreadItem(app, api, db, items[position]) // Todo: SharedItems.unreadItem(app, api, db, items[position])
// TODO: update db
} }
notifyItemChanged(position) notifyItemChanged(position)
markSnackbar(position) if (showSnackbar) {
markSnackbar(position)
}
} }
fun addItemAtIndex(item: SelfossModel.Item, position: Int) { fun addItemAtIndex(item: SelfossModel.Item, position: Int) {

View File

@ -28,7 +28,6 @@ import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailabl
import bou.amine.apps.readerforselfossv2.rest.SelfossApi import bou.amine.apps.readerforselfossv2.rest.SelfossApi
import bou.amine.apps.readerforselfossv2.rest.SelfossModel import bou.amine.apps.readerforselfossv2.rest.SelfossModel
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
import bou.amine.apps.readerforselfossv2.service.SearchService import bou.amine.apps.readerforselfossv2.service.SearchService
import bou.amine.apps.readerforselfossv2.service.SelfossService import bou.amine.apps.readerforselfossv2.service.SelfossService
import bou.amine.apps.readerforselfossv2.utils.DateUtils import bou.amine.apps.readerforselfossv2.utils.DateUtils
@ -109,21 +108,33 @@ override fun doWork(): Result {
} }
} }
service.getAndStoreAllItems(context.isNetworkAvailable()) if (context.isNetworkAvailable()) {
// TODO: SharedItems.updateDatabase(db, dateUtils) launch {
storeItems(notifyNewItems, notificationManager) try {
val newItems = service.allNewItems()
handleNewItemsNotification(newItems, notifyNewItems, notificationManager)
val readItems = service.allReadItems()
val starredItems = service.allStarredItems()
// TODO: save all to DB
} catch (e: Throwable) {}
}
}
} }
} }
} }
return Result.success() return Result.success()
} }
private fun storeItems(notifyNewItems: Boolean, notificationManager: NotificationManager) { private fun handleNewItemsNotification(
newItems: List<SelfossModel.Item>?,
notifyNewItems: Boolean,
notificationManager: NotificationManager
) {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
val apiItems = emptyList<SelfossModel.Item>() // TODO: SharedItems.items val apiItems = newItems.orEmpty()
val newSize = apiItems.filter { it.unread == 1 }.size val newSize = apiItems.filter { it.unread }.size
if (notifyNewItems && newSize > 0) { if (notifyNewItems && newSize > 0) {
val intent = Intent(context, MainActivity::class.java).apply { val intent = Intent(context, MainActivity::class.java).apply {

View File

@ -111,7 +111,9 @@ class ArticleFragment : Fragment() {
service = SelfossService(SelfossApi(apiDetailsService), dbService, SearchService(DateUtils(apiDetailsService))) service = SelfossService(SelfossApi(apiDetailsService), dbService, SearchService(DateUtils(apiDetailsService)))
item = requireArguments().getParcelable(ARG_ITEMS)!! val pi: ParecelableItem = requireArguments().getParcelable(ARG_ITEMS)!!
item = pi.toModel()
db = Room.databaseBuilder( db = Room.databaseBuilder(
requireContext(), requireContext(),
@ -187,17 +189,12 @@ class ArticleFragment : Fragment() {
R.id.share_action -> requireActivity().shareLink(url, contentTitle) R.id.share_action -> requireActivity().shareLink(url, contentTitle)
R.id.open_action -> requireActivity().openInBrowserAsNewTask(this@ArticleFragment.item) R.id.open_action -> requireActivity().openInBrowserAsNewTask(this@ArticleFragment.item)
R.id.unread_action -> if (context != null) { R.id.unread_action -> if (context != null) {
if (this@ArticleFragment.item.unread == 1) { if (this@ArticleFragment.item.unread) {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
// TODO: api.markAsRead(this@ArticleFragment.item.id.toString())
// dbService.readItem( // TODO: Update in DB
// context!!,
// api,
// db,
// this@ArticleFragment.item
// )
} }
this@ArticleFragment.item.unread = 0 this@ArticleFragment.item.unread = false
Toast.makeText( Toast.makeText(
context, context,
R.string.marked_as_read, R.string.marked_as_read,
@ -205,15 +202,10 @@ class ArticleFragment : Fragment() {
).show() ).show()
} else { } else {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
// TODO api.unmarkAsRead(this@ArticleFragment.item.id.toString())
// .unreadItem( // TODO: Update in DB
// context!!,
// api,
// db,
// this@ArticleFragment.item
// )
} }
this@ArticleFragment.item.unread = 1 this@ArticleFragment.item.unread = true
Toast.makeText( Toast.makeText(
context, context,
R.string.marked_as_unread, R.string.marked_as_unread,

View File

@ -1,7 +1,9 @@
package bou.amine.apps.readerforselfossv2.android.model package bou.amine.apps.readerforselfossv2.android.model
import android.os.Build
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import androidx.annotation.RequiresApi
import bou.amine.apps.readerforselfossv2.rest.SelfossModel import bou.amine.apps.readerforselfossv2.rest.SelfossModel
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
@ -17,15 +19,29 @@ fun SelfossModel.Item.toParcelable() : ParecelableItem =
this.icon, this.icon,
this.link, this.link,
this.sourcetitle, this.sourcetitle,
this.tags this.tags.joinToString(",")
)
fun ParecelableItem.toModel() : SelfossModel.Item =
SelfossModel.Item(
this.id,
this.datetime,
this.title,
this.content,
this.unread,
this.starred,
this.thumbnail,
this.icon,
this.link,
this.sourcetitle,
this.tags.split(",")
) )
data class ParecelableItem( data class ParecelableItem(
@SerializedName("id") val id: String, @SerializedName("id") val id: Int,
@SerializedName("datetime") val datetime: String, @SerializedName("datetime") val datetime: String,
@SerializedName("title") val title: String, @SerializedName("title") val title: String,
@SerializedName("content") val content: String, @SerializedName("content") val content: String,
@SerializedName("unread") var unread: Int, @SerializedName("unread") var unread: Boolean,
@SerializedName("starred") var starred: Int, @SerializedName("starred") var starred: Boolean,
@SerializedName("thumbnail") val thumbnail: String?, @SerializedName("thumbnail") val thumbnail: String?,
@SerializedName("icon") val icon: String?, @SerializedName("icon") val icon: String?,
@SerializedName("link") val link: String, @SerializedName("link") val link: String,
@ -42,12 +58,12 @@ data class ParecelableItem(
} }
constructor(source: Parcel) : this( constructor(source: Parcel) : this(
id = source.readString().orEmpty(), id = source.readInt(),
datetime = source.readString().orEmpty(), datetime = source.readString().orEmpty(),
title = source.readString().orEmpty(), title = source.readString().orEmpty(),
content = source.readString().orEmpty(), content = source.readString().orEmpty(),
unread = source.readInt(), unread = source.readByte().toInt() != 0,
starred = source.readInt(), starred = source.readByte().toInt() != 0,
thumbnail = source.readString(), thumbnail = source.readString(),
icon = source.readString(), icon = source.readString(),
link = source.readString().orEmpty(), link = source.readString().orEmpty(),
@ -58,12 +74,12 @@ data class ParecelableItem(
override fun describeContents() = 0 override fun describeContents() = 0
override fun writeToParcel(dest: Parcel, flags: Int) { override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeString(id) dest.writeInt(id)
dest.writeString(datetime) dest.writeString(datetime)
dest.writeString(title) dest.writeString(title)
dest.writeString(content) dest.writeString(content)
dest.writeInt(unread) dest.writeByte(if (unread) 1 else 0)
dest.writeInt(starred) dest.writeByte(if (starred) 1 else 0)
dest.writeString(thumbnail) dest.writeString(thumbnail)
dest.writeString(icon) dest.writeString(icon)
dest.writeString(link) dest.writeString(link)

View File

@ -23,11 +23,11 @@ class AndroidDeviceDatabaseService(db: AndroidDeviceDatabase, searchService: Sea
} }
override fun appendNewItems(newItems: List<SelfossModel.Item>) { override fun appendNewItems(newItems: List<SelfossModel.Item>) {
var tmpItems = items var oldItems = items
if (tmpItems != newItems) { if (oldItems != newItems) {
tmpItems = tmpItems.filter { item -> newItems.find { it.id == item.id } == null } as ArrayList<SelfossModel.Item> oldItems = oldItems.filter { item -> newItems.find { it.id == item.id } == null } as ArrayList<SelfossModel.Item>
tmpItems.addAll(newItems) oldItems.addAll(newItems)
items = tmpItems items = oldItems
sortItems() sortItems()
getFocusedItems() getFocusedItems()

View File

@ -8,40 +8,41 @@ import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
class AndroidApiDetailsService(c: Context) : ApiDetailsService { class AndroidApiDetailsService(c: Context) : ApiDetailsService {
val settings: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(c) val settings: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(c)
private var apiVersion: Int = -1 private var _apiVersion: Int = -1
private var baseUrl: String = "" private var _baseUrl: String = ""
private var userName: String = "" private var _userName: String = ""
private var password: String = "" private var _password: String = ""
override fun logApiCalls(message: String) { override fun logApiCalls(message: String) {
Log.d("LogApiCalls", message) Log.d("LogApiCalls", message)
} }
override fun getApiVersion(): Int { override fun getApiVersion(): Int {
if (apiVersion != -1) { if (_apiVersion == -1) {
apiVersion = settings.getInt("apiVersion", -1)!! _apiVersion = settings.getInt("apiVersionMajor", -1)!!
return settings.getInt("apiVersionMajor", -1)!!
} }
return apiVersion return _apiVersion
} }
override fun getBaseUrl(): String { override fun getBaseUrl(): String {
if (baseUrl.isEmpty()) { if (_baseUrl.isEmpty()) {
baseUrl = settings.getString("url", "")!! _baseUrl = settings.getString("url", "")!!
} }
return baseUrl return _baseUrl
} }
override fun getUserName(): String { override fun getUserName(): String {
if (userName.isEmpty()) { if (_userName.isEmpty()) {
userName = settings.getString("login", "")!! _userName = settings.getString("login", "")!!
} }
return userName return _userName
} }
override fun getPassword(): String { override fun getPassword(): String {
if (password.isEmpty()) { if (_password.isEmpty()) {
password = settings.getString("password", "")!! _password = settings.getString("password", "")!!
} }
return password return _password
} }
} }

View File

@ -24,15 +24,6 @@ fun SelfossModel.Item.sourceAndDateText(dateUtils: DateUtils): String {
} }
fun SelfossModel.Item.toggleStar(): SelfossModel.Item { fun SelfossModel.Item.toggleStar(): SelfossModel.Item {
this.starred = if (this.starred == 0) 1 else 0 this.starred = !this.starred
return this return this
} }
fun List<SelfossModel.Item>.flattenTags(): List<SelfossModel.Item> =
this.flatMap {
val item = it
val tags: List<String> = it.tags.split(",")
tags.map { t ->
item.copy(tags = t.trim())
}
}

View File

@ -16,7 +16,7 @@ fun TagEntity.toView(): SelfossModel.Tag =
fun SourceEntity.toView(): SelfossModel.Source = fun SourceEntity.toView(): SelfossModel.Source =
SelfossModel.Source( SelfossModel.Source(
this.id, this.id.toInt(),
this.title, this.title,
this.tags.split(","), this.tags.split(","),
this.spout, this.spout,
@ -26,7 +26,7 @@ fun SourceEntity.toView(): SelfossModel.Source =
fun SelfossModel.Source.toEntity(): SourceEntity = fun SelfossModel.Source.toEntity(): SourceEntity =
SourceEntity( SourceEntity(
this.id, this.id.toString(),
this.getTitleDecoded(), this.getTitleDecoded(),
this.tags.joinToString(","), this.tags.joinToString(","),
this.spout, this.spout,
@ -43,30 +43,30 @@ fun SelfossModel.Tag.toEntity(): TagEntity =
fun AndroidItemEntity.toView(): SelfossModel.Item = fun AndroidItemEntity.toView(): SelfossModel.Item =
SelfossModel.Item( SelfossModel.Item(
this.id, this.id.toInt(),
this.datetime, this.datetime,
this.title, this.title,
this.content, this.content,
if (this.unread) 1 else 0, this.unread,
if (this.starred) 1 else 0, this.starred,
this.thumbnail, this.thumbnail,
this.icon, this.icon,
this.link, this.link,
this.sourcetitle, this.sourcetitle,
this.tags this.tags.split(",")
) )
fun SelfossModel.Item.toEntity(): AndroidItemEntity = fun SelfossModel.Item.toEntity(): AndroidItemEntity =
AndroidItemEntity( AndroidItemEntity(
this.id, this.id.toString(),
this.datetime, this.datetime,
this.getTitleDecoded(), this.getTitleDecoded(),
this.content, this.content,
this.unread == 1, this.unread,
this.starred == 1, this.starred,
this.thumbnail, this.thumbnail,
this.icon, this.icon,
this.link, this.link,
this.getSourceTitle(), this.getSourceTitle(),
this.tags this.tags.joinToString(",")
) )

View File

@ -7,6 +7,7 @@ import io.ktor.client.engine.*
import io.ktor.client.engine.ProxyBuilder.http import io.ktor.client.engine.ProxyBuilder.http
import io.ktor.client.plugins.auth.* import io.ktor.client.plugins.auth.*
import io.ktor.client.plugins.auth.providers.* import io.ktor.client.plugins.auth.providers.*
import io.ktor.client.plugins.cache.*
import io.ktor.client.request.* import io.ktor.client.request.*
import io.ktor.client.request.forms.* import io.ktor.client.request.forms.*
import io.ktor.http.* import io.ktor.http.*
@ -19,6 +20,7 @@ class SelfossApi(private val apiDetailsService: ApiDetailsService) {
private val client = HttpClient() { private val client = HttpClient() {
install(ContentNegotiation) { install(ContentNegotiation) {
install(HttpCache)
json(Json { json(Json {
prettyPrint = true prettyPrint = true
isLenient = true isLenient = true
@ -100,7 +102,7 @@ class SelfossApi(private val apiDetailsService: ApiDetailsService) {
}.body() }.body()
suspend fun spouts(): Map<String, SelfossModel.Spout>? = suspend fun spouts(): Map<String, SelfossModel.Spout>? =
client.get(url("/a/spouts")) { client.get(url("/sources/spouts")) {
parameter("username", apiDetailsService.getUserName()) parameter("username", apiDetailsService.getUserName())
parameter("password", apiDetailsService.getPassword()) parameter("password", apiDetailsService.getPassword())
}.body() }.body()
@ -115,55 +117,38 @@ class SelfossApi(private val apiDetailsService: ApiDetailsService) {
client.get(url("/api/about")).body() client.get(url("/api/about")).body()
suspend fun markAsRead(id: String): SelfossModel.SuccessResponse? = suspend fun markAsRead(id: String): SelfossModel.SuccessResponse? =
client.submitForm( client.post(url("/mark/$id")) {
url = url("/mark/$id"), parameter("username", apiDetailsService.getUserName())
formParameters = Parameters.build { parameter("password", apiDetailsService.getPassword())
append("username", apiDetailsService.getUserName()) }.body()
append("password", apiDetailsService.getPassword())
},
encodeInQuery = true
).body()
suspend fun unmarkAsRead(id: String): SelfossModel.SuccessResponse? = suspend fun unmarkAsRead(id: String): SelfossModel.SuccessResponse? =
client.submitForm( client.post(url("/unmark/$id")) {
url = url("/unmark/$id"), parameter("username", apiDetailsService.getUserName())
formParameters = Parameters.build { parameter("password", apiDetailsService.getPassword())
append("username", apiDetailsService.getUserName()) }.body()
append("password", apiDetailsService.getPassword())
},
encodeInQuery = true
).body()
suspend fun starr(id: String): SelfossModel.SuccessResponse? = suspend fun starr(id: String): SelfossModel.SuccessResponse? =
client.submitForm( client.post(url("/starr/$id")) {
url = url("/starr/$id"), parameter("username", apiDetailsService.getUserName())
formParameters = Parameters.build { parameter("password", apiDetailsService.getPassword())
append("username", apiDetailsService.getUserName()) }.body()
append("password", apiDetailsService.getPassword())
},
encodeInQuery = true
).body()
suspend fun unstarr(id: String): SelfossModel.SuccessResponse? = suspend fun unstarr(id: String): SelfossModel.SuccessResponse? =
client.submitForm( client.post(url("/unstarr/$id")) {
url = url("/unstarr/$id"), parameter("username", apiDetailsService.getUserName())
formParameters = Parameters.build { parameter("password", apiDetailsService.getPassword())
append("username", apiDetailsService.getUserName()) }.body()
append("password", apiDetailsService.getPassword())
},
encodeInQuery = true
).body()
suspend fun markAllAsRead(ids: List<String>): SelfossModel.SuccessResponse? = suspend fun markAllAsRead(ids: List<String>): SelfossModel.SuccessResponse? =
client.submitForm( client.submitForm(
url = url("/mark"), url = url("/mark"),
formParameters = Parameters.build { formParameters = Parameters.build {
append("username", apiDetailsService.getUserName()) append("username", apiDetailsService.getUserName())
append("password", apiDetailsService.getPassword()) append("password", apiDetailsService.getPassword())
append("ids[]", ids.joinToString(",")) ids.map { append("ids[]", it) }
}, }
encodeInQuery = true ).body()
).body()
suspend fun createSourceForVersion( suspend fun createSourceForVersion(
title: String, title: String,
@ -186,19 +171,15 @@ class SelfossApi(private val apiDetailsService: ApiDetailsService) {
tags: String, tags: String,
filter: String filter: String
): SelfossModel.SuccessResponse? = ): SelfossModel.SuccessResponse? =
client.submitForm( client.post(url("/source")) {
url = url("/source"), parameter("username", apiDetailsService.getUserName())
formParameters = Parameters.build { parameter("password", apiDetailsService.getPassword())
append("username", apiDetailsService.getUserName()) parameter("title", title)
append("password", apiDetailsService.getPassword()) parameter("url", url)
append("title", title) parameter("spout", spout)
append("url", url) parameter("tags", tags)
append("spout", spout) parameter("filter", filter)
append("tags", tags) }.body()
append("filter", filter)
},
encodeInQuery = true
).body()
private suspend fun createSource2( private suspend fun createSource2(
title: String, title: String,
@ -207,21 +188,17 @@ class SelfossApi(private val apiDetailsService: ApiDetailsService) {
tags: String, tags: String,
filter: String filter: String
): SelfossModel.SuccessResponse? = ): SelfossModel.SuccessResponse? =
client.submitForm( client.post(url("/source")) {
url = url("/source"), parameter("username", apiDetailsService.getUserName())
formParameters = Parameters.build { parameter("password", apiDetailsService.getPassword())
append("username", apiDetailsService.getUserName()) parameter("title", title)
append("password", apiDetailsService.getPassword()) parameter("url", url)
append("title", title) parameter("spout", spout)
append("url", url) parameter("tags[]", tags)
append("spout", spout) parameter("filter", filter)
append("tags[]", tags) }.body()
append("filter", filter)
},
encodeInQuery = true
).body()
suspend fun deleteSource(id: String): SelfossModel.SuccessResponse? = suspend fun deleteSource(id: Int): SelfossModel.SuccessResponse? =
client.delete(url("/source/$id")) { client.delete(url("/source/$id")) {
parameter("username", apiDetailsService.getUserName()) parameter("username", apiDetailsService.getUserName())
parameter("password", apiDetailsService.getPassword()) parameter("password", apiDetailsService.getPassword())

View File

@ -52,7 +52,7 @@ class SelfossModel {
@Serializable @Serializable
data class Source( data class Source(
val id: String, val id: Int,
val title: String, val title: String,
val tags: List<String>, val tags: List<String>,
val spout: String, val spout: String,
@ -62,16 +62,16 @@ class SelfossModel {
@Serializable @Serializable
data class Item( data class Item(
val id: String, val id: Int,
val datetime: String, val datetime: String,
val title: String, val title: String,
val content: String, val content: String,
var unread: Int, var unread: Boolean,
var starred: Int, var starred: Boolean,
val thumbnail: String?, val thumbnail: String?,
val icon: String?, val icon: String?,
val link: String, val link: String,
val sourcetitle: String, val sourcetitle: String,
val tags: String val tags: List<String>
) )
} }

View File

@ -27,8 +27,8 @@ abstract class DeviceDataBaseService<ItemEntity>(val db: DeviceDatabase<ItemEnti
// This filtered items from items val. Do not use // This filtered items from items val. Do not use
fun getFocusedItems() {} fun getFocusedItems() {}
fun computeBadges() { fun computeBadges() {
searchService.badgeUnread = items.filter { item -> item.unread == 1 }.size searchService.badgeUnread = items.filter { item -> item.unread }.size
searchService.badgeStarred = items.filter { item -> item.starred == 1 }.size searchService.badgeStarred = items.filter { item -> item.starred }.size
searchService.badgeAll = items.size searchService.badgeAll = items.size
} }
} }

View File

@ -21,10 +21,6 @@ class SearchService(val dateUtils: DateUtils) {
var tagFilter: String? = null var tagFilter: String? = null
var itemsCaching = false var itemsCaching = false
var fetchedUnread = false
var fetchedAll = false
var fetchedStarred = false
var badgeUnread = -1 var badgeUnread = -1
var badgeAll = -1 var badgeAll = -1
var badgeStarred = -1 var badgeStarred = -1

View File

@ -7,90 +7,48 @@ import kotlinx.coroutines.*
class SelfossService<ItemEntity>(val api: SelfossApi, private val dbService: DeviceDataBaseService<ItemEntity>, private val searchService: SearchService) { class SelfossService<ItemEntity>(val api: SelfossApi, private val dbService: DeviceDataBaseService<ItemEntity>, private val searchService: SearchService) {
suspend fun getAndStoreAllItems(isNetworkAvailable: Boolean) = withContext( suspend fun getReadItems(itemsNumber: Int, offset: Int, isNetworkAvailable: Boolean): List<SelfossModel.Item>? = withContext(
Dispatchers.Default) { Dispatchers.Default) {
if (isNetworkAvailable) { if (isNetworkAvailable) {
launch { val apiItems = readItems( itemsNumber, offset)
try { // SAVE OR UPDATE IN DB
enqueueArticles(allNewItems(), true) return@withContext apiItems
} catch (e: Throwable) {}
}
launch {
try {
enqueueArticles(allReadItems(), false)
} catch (e: Throwable) {}
}
launch {
try {
enqueueArticles(allStarredItems(), false)
} catch (e: Throwable) {}
}
} else { } else {
launch { dbService.updateDatabase() } // GET FROM DB
return@withContext emptyList()
} }
} }
suspend fun refreshFocusedItems(itemsNumber: Int, isNetworkAvailable: Boolean) = withContext( suspend fun getUnreadItems(itemsNumber: Int, offset: Int, isNetworkAvailable: Boolean): List<SelfossModel.Item>? = withContext(
Dispatchers.Default) { Dispatchers.Default) {
if (isNetworkAvailable) { if (isNetworkAvailable) {
val response = when (searchService.displayedItems) { val apiItems = newItems(itemsNumber, offset)
"read" -> readItems(itemsNumber, 0) // SAVE OR UPDATE IN DB
"unread" -> newItems(itemsNumber, 0) return@withContext apiItems
"starred" -> starredItems(itemsNumber, 0) } else {
else -> readItems(itemsNumber, 0) // GET FROM DB
} return@withContext emptyList()
if (response != null) {
// TODO:
// dbService.refreshFocusedItems(response.body() as ArrayList<SelfossModel.Item>)
dbService.updateDatabase()
}
} }
} }
suspend fun getReadItems(itemsNumber: Int, offset: Int, isNetworkAvailable: Boolean) = withContext( suspend fun getStarredItems(itemsNumber: Int, offset: Int, isNetworkAvailable: Boolean): List<SelfossModel.Item>? = withContext(
Dispatchers.Default) { Dispatchers.Default) {
if (isNetworkAvailable) { if (isNetworkAvailable) {
try { val apiItems = starredItems(itemsNumber, offset)
enqueueArticles(readItems( itemsNumber, offset), false) // SAVE OR UPDATE IN DB
searchService.fetchedAll = true return@withContext apiItems
dbService.updateDatabase() } else {
} catch (e: Throwable) {} // GET FROM DB
return@withContext emptyList()
} }
} }
suspend fun getUnreadItems(itemsNumber: Int, offset: Int, isNetworkAvailable: Boolean) = withContext( suspend fun readAll(ids: List<String>, isNetworkAvailable: Boolean): Boolean {
Dispatchers.Default) { // Add ids params
if (isNetworkAvailable) {
try {
if (!searchService.fetchedUnread) {
dbService.clearDBItems()
}
enqueueArticles(newItems(itemsNumber, offset), false)
searchService.fetchedUnread = true
} catch (e: Throwable) {}
}
dbService.updateDatabase()
}
suspend fun getStarredItems(itemsNumber: Int, offset: Int, isNetworkAvailable: Boolean) = withContext(
Dispatchers.Default) {
if (isNetworkAvailable) {
try {
enqueueArticles(starredItems(itemsNumber, offset), false)
searchService.fetchedStarred = true
dbService.updateDatabase()
} catch (e: Throwable) {
}
}
}
suspend fun readAll(isNetworkAvailable: Boolean): Boolean {
var success = false var success = false
if (isNetworkAvailable) { if (isNetworkAvailable) {
// Do api call to read all success = api.markAllAsRead(ids)?.isSuccess == true
} else { // SAVE OR UPDATE IN DB
// Do db call to read all
} }
// refresh view // refresh view
return success return success
@ -100,7 +58,6 @@ class SelfossService<ItemEntity>(val api: SelfossApi, private val dbService: Dev
if (isNetworkAvailable) { if (isNetworkAvailable) {
try { try {
val response = api.stats() val response = api.stats()
if (response != null) { if (response != null) {
searchService.badgeUnread = response.unread searchService.badgeUnread = response.unread
searchService.badgeAll = response.total searchService.badgeAll = response.total
@ -123,13 +80,13 @@ class SelfossService<ItemEntity>(val api: SelfossApi, private val dbService: Dev
} }
} }
private suspend fun allNewItems(): List<SelfossModel.Item>? = suspend fun allNewItems(): List<SelfossModel.Item>? =
readItems(200, 0) readItems(200, 0)
private suspend fun allReadItems(): List<SelfossModel.Item>? = suspend fun allReadItems(): List<SelfossModel.Item>? =
newItems(200, 0) newItems(200, 0)
private suspend fun allStarredItems(): List<SelfossModel.Item>? = suspend fun allStarredItems(): List<SelfossModel.Item>? =
starredItems(200, 0) starredItems(200, 0)
private suspend fun readItems( private suspend fun readItems(