diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/AddSourceActivity.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/AddSourceActivity.kt index f2b8161..681b285 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/AddSourceActivity.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/AddSourceActivity.kt @@ -133,7 +133,7 @@ class AddSourceActivity : AppCompatActivity() { } - CoroutineScope(Dispatchers.IO).launch { + CoroutineScope(Dispatchers.Main).launch { var items = api!!.spouts() if (items != null) { @@ -192,7 +192,7 @@ class AddSourceActivity : AppCompatActivity() { Toast.makeText(this, R.string.form_not_complete, Toast.LENGTH_SHORT).show() } else -> { - CoroutineScope(Dispatchers.IO).launch { + CoroutineScope(Dispatchers.Main).launch { val response: SelfossModel.SuccessResponse? = api.createSourceForVersion( title, url, diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt index 919430c..81fd1b9 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt @@ -235,7 +235,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener { lastFetchDone = false handleDrawerItems() CoroutineScope(Dispatchers.Main).launch { - service.refreshFocusedItems(itemsNumber, applicationContext.isNetworkAvailable()) getElementsAccordingToTab() binding.swipeRefreshLayout.isRefreshing = false } @@ -276,7 +275,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener { reloadBadgeContent() - val tagHashes = i.tags.split(",").map { it.longHash() } + val tagHashes = i.tags.map { it.longHash() } tagsBadge = tagsBadge.map { if (tagHashes.contains(it.key)) { (it.key to (it.value - 1)) @@ -868,9 +867,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener { override fun onTabUnselected(position: Int) = Unit override fun onTabReselected(position: Int) { - val layoutManager = binding.recyclerView.adapter - when (layoutManager) { + when (val layoutManager = binding.recyclerView.adapter) { is StaggeredGridLayoutManager -> if (layoutManager.findFirstCompletelyVisibleItemPositions(null)[0] == 0) { getElementsAccordingToTab() @@ -902,10 +900,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener { private fun fetchOnEmptyList() { binding.recyclerView.doOnNextLayout { - // Todo: -// if (SharedItems.focusedItems.size - 1 == getLastVisibleItem()) { -// getElementsAccordingToTab(true) -// } + // TODO: do if last element (or is empty ?) + getElementsAccordingToTab(true) } } @@ -927,8 +923,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener { } private fun getLastVisibleItem() : Int { - val manager = binding.recyclerView.layoutManager - return when (manager) { + return when (val manager = binding.recyclerView.layoutManager) { is StaggeredGridLayoutManager -> manager.findLastCompletelyVisibleItemPositions( null ).last() @@ -945,8 +940,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener { } private fun getElementsAccordingToTab( - appendResults: Boolean = false, - offsetOverride: Int? = null + appendResults: Boolean = false ) { fun doGetAccordingToTab() { when (elementsShown) { @@ -957,12 +951,11 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener { } } - // Todo: -// offset = if (appendResults) { -// SharedItems.focusedItems.size - 1 -// } else { -// 0 -// } + offset = if (appendResults && items.size > 0) { + items.size - 1 + } else { + 0 + } firstVisible = if (appendResults) firstVisible else 0 doGetAccordingToTab() @@ -970,41 +963,42 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener { private fun getUnRead(appendResults: Boolean = false) { CoroutineScope(Dispatchers.Main).launch { - // Todo: -// if (appendResults || !SharedItems.fetchedUnread) { -// binding.swipeRefreshLayout.isRefreshing = true -// service.getUnreadItems(itemsNumber, offset, applicationContext.isNetworkAvailable()) -// binding.swipeRefreshLayout.isRefreshing = false -// } - // Todo: SharedItems.getUnRead() - // Todo: items = SharedItems.focusedItems + binding.swipeRefreshLayout.isRefreshing = true + val apiItems = service.getUnreadItems(itemsNumber, offset, applicationContext.isNetworkAvailable()) + if (appendResults) { + apiItems?.let { items.addAll(it) } + } else { + items = apiItems.orEmpty() as ArrayList + } + binding.swipeRefreshLayout.isRefreshing = false handleListResult() } } private fun getRead(appendResults: Boolean = false) { CoroutineScope(Dispatchers.Main).launch { - // Todo: -// if (appendResults || !SharedItems.fetchedAll) { -// binding.swipeRefreshLayout.isRefreshing = true -// service.getReadItems(itemsNumber, offset, applicationContext.isNetworkAvailable()) -// binding.swipeRefreshLayout.isRefreshing = false -// } -// SharedItems.getAll() -// items = SharedItems.focusedItems + binding.swipeRefreshLayout.isRefreshing = true + val apiItems = service.getReadItems(itemsNumber, offset, applicationContext.isNetworkAvailable()) + if (appendResults) { + apiItems?.let { items.addAll(it) } + } else { + items = apiItems.orEmpty() as ArrayList + } + binding.swipeRefreshLayout.isRefreshing = false handleListResult() } } private fun getStarred(appendResults: Boolean = false) { CoroutineScope(Dispatchers.Main).launch { - if (appendResults || !searchService.fetchedStarred) { - binding.swipeRefreshLayout.isRefreshing = true - service.getStarredItems(itemsNumber, offset, applicationContext.isNetworkAvailable()) - binding.swipeRefreshLayout.isRefreshing = false + binding.swipeRefreshLayout.isRefreshing = true + val apiItems = service.getStarredItems(itemsNumber, offset, applicationContext.isNetworkAvailable()) + if (appendResults) { + apiItems?.let { items.addAll(it) } + } else { + items = apiItems.orEmpty() as ArrayList } - // Todo: SharedItems.getStarred() - // Todo: items = SharedItems.focusedItems + binding.swipeRefreshLayout.isRefreshing = false handleListResult() } } @@ -1069,7 +1063,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener { } binding.recyclerView.adapter = recyclerAdapter } else { - (recyclerAdapter as ItemsAdapter<*>).updateAllItems() + (recyclerAdapter as ItemsAdapter<*>).updateAllItems(items) } reloadBadges() @@ -1186,7 +1180,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener { if (this@HomeActivity.isNetworkAvailable(null, offlineShortcut)) { CoroutineScope(Dispatchers.Main).launch { - val success = service.readAll(applicationContext.isNetworkAvailable()) + val success = service.readAll(items.map { it.id.toString() }, applicationContext.isNetworkAvailable()) if (success) { Toast.makeText( this@HomeActivity, diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/ReaderActivity.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/ReaderActivity.kt index d1a4129..7a945bd 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/ReaderActivity.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/ReaderActivity.kt @@ -130,7 +130,8 @@ class ReaderActivity : AppCompatActivity() { private fun readItem(item: SelfossModel.Item) { if (markOnScroll) { 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) toolbarMenu = menu - if (allItems.isNotEmpty() && allItems[currentItem].starred == 1) { + if (allItems.isNotEmpty() && allItems[currentItem].starred) { canRemoveFromFavorite() } else { canFavorite() @@ -194,7 +195,7 @@ class ReaderActivity : AppCompatActivity() { override fun onPageSelected(position: Int) { super.onPageSelected(position) - if (allItems[position].starred == 1) { + if (allItems[position].starred) { canRemoveFromFavorite() } else { canFavorite() @@ -225,26 +226,16 @@ class ReaderActivity : AppCompatActivity() { return true } R.id.star -> { - if (allItems[binding.pager.currentItem].starred == 1) { + if (allItems[binding.pager.currentItem].starred) { CoroutineScope(Dispatchers.IO).launch { - // Todo: -// SharedItems.unstarItem( -// this@ReaderActivity, -// api, -// db, -// allItems[binding.pager.currentItem] -// ) + api.unstarr(allItems[binding.pager.currentItem].id.toString()) + // TODO: update in DB } afterUnsave() } else { CoroutineScope(Dispatchers.IO).launch { - // Todo: -// SharedItems.starItem( -// this@ReaderActivity, -// api, -// db, -// allItems[binding.pager.currentItem] -// ) + api.starr(allItems[binding.pager.currentItem].id.toString()) + // TODO: update in DB } afterSave() } diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/SourcesActivity.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/SourcesActivity.kt index bc45148..eb949e0 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/SourcesActivity.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/SourcesActivity.kt @@ -80,7 +80,7 @@ class SourcesActivity : AppCompatActivity() { binding.recyclerView.layoutManager = mLayoutManager if (this@SourcesActivity.isNetworkAvailable(binding.recyclerView)) { - CoroutineScope(Dispatchers.IO).launch { + CoroutineScope(Dispatchers.Main).launch { val response = api.sources() if (response != null) { items = response diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/ItemCardAdapter.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/ItemCardAdapter.kt index a10b8d8..368e7d7 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/ItemCardAdapter.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/ItemCardAdapter.kt @@ -59,7 +59,7 @@ class ItemCardAdapter( with(holder) { val itm = items[position] - binding.favButton.isSelected = itm.starred == 1 + binding.favButton.isSelected = itm.starred binding.title.text = itm.getTitleDecoded() binding.title.setOnTouchListener(LinkOnTouchListener()) @@ -112,17 +112,19 @@ class ItemCardAdapter( binding.favButton.setOnClickListener { val item = items[bindingAdapterPosition] if (c.isNetworkAvailable()) { - if (item.starred == 1) { + if (item.starred) { 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 } else { 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 } } diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/ItemsAdapter.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/ItemsAdapter.kt index 4f97d93..82a3cd1 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/ItemsAdapter.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/ItemsAdapter.kt @@ -29,10 +29,10 @@ abstract class ItemsAdapter : RecyclerView.Adapte abstract val searchService: SearchService abstract val updateItems: (ArrayList) -> Unit - fun updateAllItems() { - items = ArrayList() // TODO: SharedItems.focusedItems + fun updateAllItems(items: ArrayList) { + this.items = items notifyDataSetChanged() - updateItems(items) + updateItems(this.items) } private fun unmarkSnackbar(i: SelfossModel.Item, position: Int) { @@ -44,14 +44,8 @@ abstract class ItemsAdapter : RecyclerView.Adapte ) .setAction(R.string.undo_string) { 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 @@ -68,17 +62,7 @@ abstract class ItemsAdapter : RecyclerView.Adapte Snackbar.LENGTH_LONG ) .setAction(R.string.undo_string) { - CoroutineScope(Dispatchers.IO).launch { - // Todo: SharedItems.readItem(app, api, db, items[position]) - } - // Todo: items = SharedItems.focusedItems - // Todo: -// if (SharedItems.displayedItems == "unread") { -// notifyItemRemoved(position) -// updateItems(items) -// } else { -// notifyItemChanged(position) -// } + readItemAtIndex(position) } val view = s.view @@ -88,18 +72,19 @@ abstract class ItemsAdapter : RecyclerView.Adapte } fun handleItemAtIndex(position: Int) { - // Todo: -// if (SharedItems.unreadItemStatusAtIndex(position)) { -// readItemAtIndex(position) -// } else { -// unreadItemAtIndex(position) -// } + if (items[position].unread) { + readItemAtIndex(position) + } else { + unreadItemAtIndex(position) + } } - private fun readItemAtIndex(position: Int) { + private fun readItemAtIndex(position: Int, showSnackbar: Boolean = true) { val i = items[position] CoroutineScope(Dispatchers.IO).launch { - // Todo: SharedItems.readItem(app, api, db, i) + api.markAsRead(i.id.toString()) + // TODO: update db + } // Todo: // if (SharedItems.displayedItems == "unread") { @@ -109,15 +94,22 @@ abstract class ItemsAdapter : RecyclerView.Adapte // } else { // 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 { + api.unmarkAsRead(items[position].id.toString()) // Todo: SharedItems.unreadItem(app, api, db, items[position]) + // TODO: update db + } notifyItemChanged(position) - markSnackbar(position) + if (showSnackbar) { + markSnackbar(position) + } } fun addItemAtIndex(item: SelfossModel.Item, position: Int) { diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/background/background.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/background/background.kt index 47aa14f..e929f99 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/background/background.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/background/background.kt @@ -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.SelfossModel -import bou.amine.apps.readerforselfossv2.service.ApiDetailsService import bou.amine.apps.readerforselfossv2.service.SearchService import bou.amine.apps.readerforselfossv2.service.SelfossService import bou.amine.apps.readerforselfossv2.utils.DateUtils @@ -109,21 +108,33 @@ override fun doWork(): Result { } } - service.getAndStoreAllItems(context.isNetworkAvailable()) - // TODO: SharedItems.updateDatabase(db, dateUtils) - storeItems(notifyNewItems, notificationManager) + if (context.isNetworkAvailable()) { + launch { + 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() } - private fun storeItems(notifyNewItems: Boolean, notificationManager: NotificationManager) { + private fun handleNewItemsNotification( + newItems: List?, + notifyNewItems: Boolean, + notificationManager: NotificationManager + ) { CoroutineScope(Dispatchers.IO).launch { - val apiItems = emptyList() // 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) { val intent = Intent(context, MainActivity::class.java).apply { diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/ArticleFragment.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/ArticleFragment.kt index 97b30ea..c9628aa 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/ArticleFragment.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/ArticleFragment.kt @@ -111,7 +111,9 @@ class ArticleFragment : Fragment() { 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( requireContext(), @@ -187,17 +189,12 @@ class ArticleFragment : Fragment() { R.id.share_action -> requireActivity().shareLink(url, contentTitle) R.id.open_action -> requireActivity().openInBrowserAsNewTask(this@ArticleFragment.item) R.id.unread_action -> if (context != null) { - if (this@ArticleFragment.item.unread == 1) { + if (this@ArticleFragment.item.unread) { CoroutineScope(Dispatchers.IO).launch { - // TODO: -// dbService.readItem( -// context!!, -// api, -// db, -// this@ArticleFragment.item -// ) + api.markAsRead(this@ArticleFragment.item.id.toString()) + // TODO: Update in DB } - this@ArticleFragment.item.unread = 0 + this@ArticleFragment.item.unread = false Toast.makeText( context, R.string.marked_as_read, @@ -205,15 +202,10 @@ class ArticleFragment : Fragment() { ).show() } else { CoroutineScope(Dispatchers.IO).launch { - // TODO -// .unreadItem( -// context!!, -// api, -// db, -// this@ArticleFragment.item -// ) + api.unmarkAsRead(this@ArticleFragment.item.id.toString()) + // TODO: Update in DB } - this@ArticleFragment.item.unread = 1 + this@ArticleFragment.item.unread = true Toast.makeText( context, R.string.marked_as_unread, diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/model/ParecelableItem.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/model/ParecelableItem.kt index 1de2e17..b3faf46 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/model/ParecelableItem.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/model/ParecelableItem.kt @@ -1,7 +1,9 @@ package bou.amine.apps.readerforselfossv2.android.model +import android.os.Build import android.os.Parcel import android.os.Parcelable +import androidx.annotation.RequiresApi import bou.amine.apps.readerforselfossv2.rest.SelfossModel import com.google.gson.annotations.SerializedName @@ -17,15 +19,29 @@ fun SelfossModel.Item.toParcelable() : ParecelableItem = this.icon, this.link, 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( - @SerializedName("id") val id: String, + @SerializedName("id") val id: Int, @SerializedName("datetime") val datetime: String, @SerializedName("title") val title: String, @SerializedName("content") val content: String, - @SerializedName("unread") var unread: Int, - @SerializedName("starred") var starred: Int, + @SerializedName("unread") var unread: Boolean, + @SerializedName("starred") var starred: Boolean, @SerializedName("thumbnail") val thumbnail: String?, @SerializedName("icon") val icon: String?, @SerializedName("link") val link: String, @@ -42,12 +58,12 @@ data class ParecelableItem( } constructor(source: Parcel) : this( - id = source.readString().orEmpty(), + id = source.readInt(), datetime = source.readString().orEmpty(), title = source.readString().orEmpty(), content = source.readString().orEmpty(), - unread = source.readInt(), - starred = source.readInt(), + unread = source.readByte().toInt() != 0, + starred = source.readByte().toInt() != 0, thumbnail = source.readString(), icon = source.readString(), link = source.readString().orEmpty(), @@ -58,12 +74,12 @@ data class ParecelableItem( override fun describeContents() = 0 override fun writeToParcel(dest: Parcel, flags: Int) { - dest.writeString(id) + dest.writeInt(id) dest.writeString(datetime) dest.writeString(title) dest.writeString(content) - dest.writeInt(unread) - dest.writeInt(starred) + dest.writeByte(if (unread) 1 else 0) + dest.writeByte(if (starred) 1 else 0) dest.writeString(thumbnail) dest.writeString(icon) dest.writeString(link) diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/persistence/AndroidDeviceDatabaseService.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/persistence/AndroidDeviceDatabaseService.kt index f7178ff..1b4fffa 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/persistence/AndroidDeviceDatabaseService.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/persistence/AndroidDeviceDatabaseService.kt @@ -23,11 +23,11 @@ class AndroidDeviceDatabaseService(db: AndroidDeviceDatabase, searchService: Sea } override fun appendNewItems(newItems: List) { - var tmpItems = items - if (tmpItems != newItems) { - tmpItems = tmpItems.filter { item -> newItems.find { it.id == item.id } == null } as ArrayList - tmpItems.addAll(newItems) - items = tmpItems + var oldItems = items + if (oldItems != newItems) { + oldItems = oldItems.filter { item -> newItems.find { it.id == item.id } == null } as ArrayList + oldItems.addAll(newItems) + items = oldItems sortItems() getFocusedItems() diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/service/AndroidApiDetailsService.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/service/AndroidApiDetailsService.kt index 96cdce3..77e6e31 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/service/AndroidApiDetailsService.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/service/AndroidApiDetailsService.kt @@ -8,40 +8,41 @@ import bou.amine.apps.readerforselfossv2.service.ApiDetailsService class AndroidApiDetailsService(c: Context) : ApiDetailsService { val settings: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(c) - private var apiVersion: Int = -1 - private var baseUrl: String = "" - private var userName: String = "" - private var password: String = "" + private var _apiVersion: Int = -1 + private var _baseUrl: String = "" + private var _userName: String = "" + private var _password: String = "" override fun logApiCalls(message: String) { Log.d("LogApiCalls", message) } override fun getApiVersion(): Int { - if (apiVersion != -1) { - apiVersion = settings.getInt("apiVersion", -1)!! + if (_apiVersion == -1) { + _apiVersion = settings.getInt("apiVersionMajor", -1)!! + return settings.getInt("apiVersionMajor", -1)!! } - return apiVersion + return _apiVersion } override fun getBaseUrl(): String { - if (baseUrl.isEmpty()) { - baseUrl = settings.getString("url", "")!! + if (_baseUrl.isEmpty()) { + _baseUrl = settings.getString("url", "")!! } - return baseUrl + return _baseUrl } override fun getUserName(): String { - if (userName.isEmpty()) { - userName = settings.getString("login", "")!! + if (_userName.isEmpty()) { + _userName = settings.getString("login", "")!! } - return userName + return _userName } override fun getPassword(): String { - if (password.isEmpty()) { - password = settings.getString("password", "")!! + if (_password.isEmpty()) { + _password = settings.getString("password", "")!! } - return password + return _password } } \ No newline at end of file diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/ItemsUtils.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/ItemsUtils.kt index e713c37..8ae737e 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/ItemsUtils.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/ItemsUtils.kt @@ -24,15 +24,6 @@ fun SelfossModel.Item.sourceAndDateText(dateUtils: DateUtils): String { } fun SelfossModel.Item.toggleStar(): SelfossModel.Item { - this.starred = if (this.starred == 0) 1 else 0 + this.starred = !this.starred return this -} - -fun List.flattenTags(): List = - this.flatMap { - val item = it - val tags: List = it.tags.split(",") - tags.map { t -> - item.copy(tags = t.trim()) - } - } \ No newline at end of file +} \ No newline at end of file diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/persistence/EntitiesUtils.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/persistence/EntitiesUtils.kt index 6f85025..d0144bc 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/persistence/EntitiesUtils.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/persistence/EntitiesUtils.kt @@ -16,7 +16,7 @@ fun TagEntity.toView(): SelfossModel.Tag = fun SourceEntity.toView(): SelfossModel.Source = SelfossModel.Source( - this.id, + this.id.toInt(), this.title, this.tags.split(","), this.spout, @@ -26,7 +26,7 @@ fun SourceEntity.toView(): SelfossModel.Source = fun SelfossModel.Source.toEntity(): SourceEntity = SourceEntity( - this.id, + this.id.toString(), this.getTitleDecoded(), this.tags.joinToString(","), this.spout, @@ -43,30 +43,30 @@ fun SelfossModel.Tag.toEntity(): TagEntity = fun AndroidItemEntity.toView(): SelfossModel.Item = SelfossModel.Item( - this.id, + this.id.toInt(), this.datetime, this.title, this.content, - if (this.unread) 1 else 0, - if (this.starred) 1 else 0, + this.unread, + this.starred, this.thumbnail, this.icon, this.link, this.sourcetitle, - this.tags + this.tags.split(",") ) fun SelfossModel.Item.toEntity(): AndroidItemEntity = AndroidItemEntity( - this.id, + this.id.toString(), this.datetime, this.getTitleDecoded(), this.content, - this.unread == 1, - this.starred == 1, + this.unread, + this.starred, this.thumbnail, this.icon, this.link, this.getSourceTitle(), - this.tags + this.tags.joinToString(",") ) \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt index ac43883..d63c722 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt @@ -7,6 +7,7 @@ import io.ktor.client.engine.* import io.ktor.client.engine.ProxyBuilder.http import io.ktor.client.plugins.auth.* import io.ktor.client.plugins.auth.providers.* +import io.ktor.client.plugins.cache.* import io.ktor.client.request.* import io.ktor.client.request.forms.* import io.ktor.http.* @@ -19,6 +20,7 @@ class SelfossApi(private val apiDetailsService: ApiDetailsService) { private val client = HttpClient() { install(ContentNegotiation) { + install(HttpCache) json(Json { prettyPrint = true isLenient = true @@ -100,7 +102,7 @@ class SelfossApi(private val apiDetailsService: ApiDetailsService) { }.body() suspend fun spouts(): Map? = - client.get(url("/a/spouts")) { + client.get(url("/sources/spouts")) { parameter("username", apiDetailsService.getUserName()) parameter("password", apiDetailsService.getPassword()) }.body() @@ -115,55 +117,38 @@ class SelfossApi(private val apiDetailsService: ApiDetailsService) { client.get(url("/api/about")).body() suspend fun markAsRead(id: String): SelfossModel.SuccessResponse? = - client.submitForm( - url = url("/mark/$id"), - formParameters = Parameters.build { - append("username", apiDetailsService.getUserName()) - append("password", apiDetailsService.getPassword()) - }, - encodeInQuery = true - ).body() + client.post(url("/mark/$id")) { + parameter("username", apiDetailsService.getUserName()) + parameter("password", apiDetailsService.getPassword()) + }.body() suspend fun unmarkAsRead(id: String): SelfossModel.SuccessResponse? = - client.submitForm( - url = url("/unmark/$id"), - formParameters = Parameters.build { - append("username", apiDetailsService.getUserName()) - append("password", apiDetailsService.getPassword()) - }, - encodeInQuery = true - ).body() + client.post(url("/unmark/$id")) { + parameter("username", apiDetailsService.getUserName()) + parameter("password", apiDetailsService.getPassword()) + }.body() suspend fun starr(id: String): SelfossModel.SuccessResponse? = - client.submitForm( - url = url("/starr/$id"), - formParameters = Parameters.build { - append("username", apiDetailsService.getUserName()) - append("password", apiDetailsService.getPassword()) - }, - encodeInQuery = true - ).body() + client.post(url("/starr/$id")) { + parameter("username", apiDetailsService.getUserName()) + parameter("password", apiDetailsService.getPassword()) + }.body() suspend fun unstarr(id: String): SelfossModel.SuccessResponse? = - client.submitForm( - url = url("/unstarr/$id"), - formParameters = Parameters.build { - append("username", apiDetailsService.getUserName()) - append("password", apiDetailsService.getPassword()) - }, - encodeInQuery = true - ).body() + client.post(url("/unstarr/$id")) { + parameter("username", apiDetailsService.getUserName()) + parameter("password", apiDetailsService.getPassword()) + }.body() suspend fun markAllAsRead(ids: List): SelfossModel.SuccessResponse? = client.submitForm( - url = url("/mark"), - formParameters = Parameters.build { - append("username", apiDetailsService.getUserName()) - append("password", apiDetailsService.getPassword()) - append("ids[]", ids.joinToString(",")) - }, - encodeInQuery = true - ).body() + url = url("/mark"), + formParameters = Parameters.build { + append("username", apiDetailsService.getUserName()) + append("password", apiDetailsService.getPassword()) + ids.map { append("ids[]", it) } + } + ).body() suspend fun createSourceForVersion( title: String, @@ -186,19 +171,15 @@ class SelfossApi(private val apiDetailsService: ApiDetailsService) { tags: String, filter: String ): SelfossModel.SuccessResponse? = - client.submitForm( - url = url("/source"), - formParameters = Parameters.build { - append("username", apiDetailsService.getUserName()) - append("password", apiDetailsService.getPassword()) - append("title", title) - append("url", url) - append("spout", spout) - append("tags", tags) - append("filter", filter) - }, - encodeInQuery = true - ).body() + client.post(url("/source")) { + parameter("username", apiDetailsService.getUserName()) + parameter("password", apiDetailsService.getPassword()) + parameter("title", title) + parameter("url", url) + parameter("spout", spout) + parameter("tags", tags) + parameter("filter", filter) + }.body() private suspend fun createSource2( title: String, @@ -207,21 +188,17 @@ class SelfossApi(private val apiDetailsService: ApiDetailsService) { tags: String, filter: String ): SelfossModel.SuccessResponse? = - client.submitForm( - url = url("/source"), - formParameters = Parameters.build { - append("username", apiDetailsService.getUserName()) - append("password", apiDetailsService.getPassword()) - append("title", title) - append("url", url) - append("spout", spout) - append("tags[]", tags) - append("filter", filter) - }, - encodeInQuery = true - ).body() + client.post(url("/source")) { + parameter("username", apiDetailsService.getUserName()) + parameter("password", apiDetailsService.getPassword()) + parameter("title", title) + parameter("url", url) + parameter("spout", spout) + parameter("tags[]", tags) + parameter("filter", filter) + }.body() - suspend fun deleteSource(id: String): SelfossModel.SuccessResponse? = + suspend fun deleteSource(id: Int): SelfossModel.SuccessResponse? = client.delete(url("/source/$id")) { parameter("username", apiDetailsService.getUserName()) parameter("password", apiDetailsService.getPassword()) diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossModel.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossModel.kt index a9c2d47..396abfa 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossModel.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossModel.kt @@ -52,7 +52,7 @@ class SelfossModel { @Serializable data class Source( - val id: String, + val id: Int, val title: String, val tags: List, val spout: String, @@ -62,16 +62,16 @@ class SelfossModel { @Serializable data class Item( - val id: String, + val id: Int, val datetime: String, val title: String, val content: String, - var unread: Int, - var starred: Int, + var unread: Boolean, + var starred: Boolean, val thumbnail: String?, val icon: String?, val link: String, val sourcetitle: String, - val tags: String + val tags: List ) } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/service/DeviceDataBaseService.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/service/DeviceDataBaseService.kt index bd14d22..f871a65 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/service/DeviceDataBaseService.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/service/DeviceDataBaseService.kt @@ -27,8 +27,8 @@ abstract class DeviceDataBaseService(val db: DeviceDatabase item.unread == 1 }.size - searchService.badgeStarred = items.filter { item -> item.starred == 1 }.size + searchService.badgeUnread = items.filter { item -> item.unread }.size + searchService.badgeStarred = items.filter { item -> item.starred }.size searchService.badgeAll = items.size } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/service/SearchService.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/service/SearchService.kt index e929eba..0530104 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/service/SearchService.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/service/SearchService.kt @@ -21,10 +21,6 @@ class SearchService(val dateUtils: DateUtils) { var tagFilter: String? = null var itemsCaching = false - var fetchedUnread = false - var fetchedAll = false - var fetchedStarred = false - var badgeUnread = -1 var badgeAll = -1 var badgeStarred = -1 diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/service/SelfossService.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/service/SelfossService.kt index ddc9b85..365abe9 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/service/SelfossService.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/service/SelfossService.kt @@ -7,90 +7,48 @@ import kotlinx.coroutines.* class SelfossService(val api: SelfossApi, private val dbService: DeviceDataBaseService, private val searchService: SearchService) { - suspend fun getAndStoreAllItems(isNetworkAvailable: Boolean) = withContext( + suspend fun getReadItems(itemsNumber: Int, offset: Int, isNetworkAvailable: Boolean): List? = withContext( Dispatchers.Default) { if (isNetworkAvailable) { - launch { - try { - enqueueArticles(allNewItems(), true) - } catch (e: Throwable) {} - } - launch { - try { - enqueueArticles(allReadItems(), false) - } catch (e: Throwable) {} - } - launch { - try { - enqueueArticles(allStarredItems(), false) - } catch (e: Throwable) {} - } + val apiItems = readItems( itemsNumber, offset) + // SAVE OR UPDATE IN DB + return@withContext apiItems } 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? = withContext( Dispatchers.Default) { if (isNetworkAvailable) { - val response = when (searchService.displayedItems) { - "read" -> readItems(itemsNumber, 0) - "unread" -> newItems(itemsNumber, 0) - "starred" -> starredItems(itemsNumber, 0) - else -> readItems(itemsNumber, 0) - } - - if (response != null) { - // TODO: - // dbService.refreshFocusedItems(response.body() as ArrayList) - dbService.updateDatabase() - } + val apiItems = newItems(itemsNumber, offset) + // SAVE OR UPDATE IN DB + return@withContext apiItems + } else { + // GET FROM DB + return@withContext emptyList() } } - suspend fun getReadItems(itemsNumber: Int, offset: Int, isNetworkAvailable: Boolean) = withContext( + suspend fun getStarredItems(itemsNumber: Int, offset: Int, isNetworkAvailable: Boolean): List? = withContext( Dispatchers.Default) { if (isNetworkAvailable) { - try { - enqueueArticles(readItems( itemsNumber, offset), false) - searchService.fetchedAll = true - dbService.updateDatabase() - } catch (e: Throwable) {} + val apiItems = starredItems(itemsNumber, offset) + // SAVE OR UPDATE IN DB + return@withContext apiItems + } else { + // GET FROM DB + return@withContext emptyList() } } - suspend fun getUnreadItems(itemsNumber: Int, offset: Int, isNetworkAvailable: Boolean) = withContext( - Dispatchers.Default) { - 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 { + suspend fun readAll(ids: List, isNetworkAvailable: Boolean): Boolean { + // Add ids params var success = false if (isNetworkAvailable) { - // Do api call to read all - } else { - // Do db call to read all + success = api.markAllAsRead(ids)?.isSuccess == true + // SAVE OR UPDATE IN DB } // refresh view return success @@ -100,7 +58,6 @@ class SelfossService(val api: SelfossApi, private val dbService: Dev if (isNetworkAvailable) { try { val response = api.stats() - if (response != null) { searchService.badgeUnread = response.unread searchService.badgeAll = response.total @@ -123,13 +80,13 @@ class SelfossService(val api: SelfossApi, private val dbService: Dev } } - private suspend fun allNewItems(): List? = + suspend fun allNewItems(): List? = readItems(200, 0) - private suspend fun allReadItems(): List? = + suspend fun allReadItems(): List? = newItems(200, 0) - private suspend fun allStarredItems(): List? = + suspend fun allStarredItems(): List? = starredItems(200, 0) private suspend fun readItems(