From d02b28b81fa6b296f37aa8769a154f1c003b2d40 Mon Sep 17 00:00:00 2001 From: Amine Date: Sun, 4 Nov 2018 14:25:05 +0100 Subject: [PATCH] Initial changes for #179. --- app/build.gradle | 4 +- .../3.json | 226 ++++++++++++++++++ .../bou/readerforselfoss/HomeActivity.kt | 46 +++- .../bou/readerforselfoss/ReaderActivity.kt | 101 +++++--- .../adapters/ItemCardAdapter.kt | 14 +- .../readerforselfoss/adapters/ItemsAdapter.kt | 46 ++-- .../readerforselfoss/background/background.kt | 3 +- .../fragments/ArticleFragment.kt | 16 ++ .../persistence/dao/ActionsDao.kt | 23 ++ .../persistence/database/AppDatabase.kt | 6 +- .../persistence/entities/ActionEntity.kt | 22 ++ .../persistence/migrations/migrations.kt | 6 + 12 files changed, 446 insertions(+), 67 deletions(-) create mode 100644 app/schemas/apps.amine.bou.readerforselfoss.persistence.database.AppDatabase/3.json create mode 100644 app/src/main/java/apps/amine/bou/readerforselfoss/persistence/dao/ActionsDao.kt create mode 100644 app/src/main/java/apps/amine/bou/readerforselfoss/persistence/entities/ActionEntity.kt diff --git a/app/build.gradle b/app/build.gradle index d40bf9a..14dbe70 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -152,8 +152,8 @@ dependencies { implementation 'androidx.core:core-ktx:1.0.0' // Crash - implementation 'ch.acra:acra-http:5.1.3' - implementation 'ch.acra:acra-dialog:5.1.3' + implementation 'ch.acra:acra-http:5.2.1' + implementation 'ch.acra:acra-dialog:5.2.1' implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" diff --git a/app/schemas/apps.amine.bou.readerforselfoss.persistence.database.AppDatabase/3.json b/app/schemas/apps.amine.bou.readerforselfoss.persistence.database.AppDatabase/3.json new file mode 100644 index 0000000..70d1621 --- /dev/null +++ b/app/schemas/apps.amine.bou.readerforselfoss.persistence.database.AppDatabase/3.json @@ -0,0 +1,226 @@ +{ + "formatVersion": 1, + "database": { + "version": 3, + "identityHash": "7ad9c4961992c13b670128485ebb3efc", + "entities": [ + { + "tableName": "tags", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tag` TEXT NOT NULL, `color` TEXT NOT NULL, `unread` INTEGER NOT NULL, PRIMARY KEY(`tag`))", + "fields": [ + { + "fieldPath": "tag", + "columnName": "tag", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "tag" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "sources", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `title` TEXT NOT NULL, `tags` TEXT NOT NULL, `spout` TEXT NOT NULL, `error` TEXT NOT NULL, `icon` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "tags", + "columnName": "tags", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "spout", + "columnName": "spout", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "error", + "columnName": "error", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "icon", + "columnName": "icon", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "items", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `datetime` TEXT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `thumbnail` TEXT NOT NULL, `icon` TEXT NOT NULL, `link` TEXT NOT NULL, `sourcetitle` TEXT NOT NULL, `tags` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "datetime", + "columnName": "datetime", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "starred", + "columnName": "starred", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "thumbnail", + "columnName": "thumbnail", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "icon", + "columnName": "icon", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "link", + "columnName": "link", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sourcetitle", + "columnName": "sourcetitle", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "tags", + "columnName": "tags", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "actions", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `articleid` TEXT NOT NULL, `read` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `unstarred` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "articleId", + "columnName": "articleid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "read", + "columnName": "read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unread", + "columnName": "unread", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "starred", + "columnName": "starred", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unstarred", + "columnName": "unstarred", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"7ad9c4961992c13b670128485ebb3efc\")" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/HomeActivity.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/HomeActivity.kt index 975b283..dcca697 100644 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/HomeActivity.kt +++ b/app/src/main/java/apps/amine/bou/readerforselfoss/HomeActivity.kt @@ -42,7 +42,9 @@ import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse import apps.amine.bou.readerforselfoss.api.selfoss.Tag import apps.amine.bou.readerforselfoss.background.LoadingWorker import apps.amine.bou.readerforselfoss.persistence.database.AppDatabase +import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_1_2 +import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_2_3 import apps.amine.bou.readerforselfoss.settings.SettingsActivity import apps.amine.bou.readerforselfoss.themes.AppColors import apps.amine.bou.readerforselfoss.themes.Toppings @@ -171,7 +173,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener { db = Room.databaseBuilder( applicationContext, AppDatabase::class.java, "selfoss-database" - ).addMigrations(MIGRATION_1_2).build() + ).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).build() customTabActivityHelper = CustomTabActivityHelper() @@ -216,7 +218,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener { recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder ): Int = - if (elementsShown != UNREAD_SHOWN || !this@HomeActivity.isNetworkAccessible(null)) { + if (elementsShown != UNREAD_SHOWN) { 0 } else { super.getSwipeDirs( @@ -349,6 +351,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener { handleGDPRDialog(sharedPref.getBoolean("GDPR_shown", false)) handleRecurringTask() + + handleOfflineActions() } private fun getAndStoreAllItems() { @@ -1289,7 +1293,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener { } } R.id.readAll -> { - if (elementsShown == UNREAD_SHOWN && this@HomeActivity.isNetworkAccessible(null)) { + if (elementsShown == UNREAD_SHOWN) { needsConfirmation(R.string.readAll, R.string.markall_dialog_message) { swipeRefreshLayout.isRefreshing = false val ids = allItems.map { it.id } @@ -1303,7 +1307,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener { ACRA.getErrorReporter().maybeHandleSilentException(e, this@HomeActivity) } - if (ids.isNotEmpty()) { + if (ids.isNotEmpty() && this@HomeActivity.isNetworkAccessible(null)) { api.readAll(ids).enqueue(object : Callback { override fun onResponse( call: Call, @@ -1428,5 +1432,39 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener { WorkManager.getInstance().enqueueUniquePeriodicWork("selfoss-loading", ExistingPeriodicWorkPolicy.KEEP, backgroundWork) } } + + private fun handleOfflineActions() { + fun doAndReportOnFail(call: Call, action: ActionEntity) { + call.enqueue(object: Callback { + override fun onResponse( + call: Call, + response: Response + ) { + thread { + db.actionsDao().delete(action) + } + } + + override fun onFailure(call: Call, t: Throwable) { + ACRA.getErrorReporter().maybeHandleSilentException(t, this@HomeActivity) + } + }) + } + + if (this@HomeActivity.isNetworkAccessible(null)) { + thread { + val actions = db.actionsDao().actions() + + actions.forEach { action -> + when { + action.read -> doAndReportOnFail(api.markItem(action.articleId), action) + action.unread -> doAndReportOnFail(api.unmarkItem(action.articleId), action) + action.starred -> doAndReportOnFail(api.starrItem(action.articleId), action) + action.unstarred -> doAndReportOnFail(api.unstarrItem(action.articleId), action) + } + } + } + } + } } diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/ReaderActivity.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/ReaderActivity.kt index ca53e96..d76bfee 100644 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/ReaderActivity.kt +++ b/app/src/main/java/apps/amine/bou/readerforselfoss/ReaderActivity.kt @@ -19,7 +19,9 @@ import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse import apps.amine.bou.readerforselfoss.fragments.ArticleFragment import apps.amine.bou.readerforselfoss.persistence.database.AppDatabase +import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_1_2 +import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_2_3 import apps.amine.bou.readerforselfoss.themes.AppColors import apps.amine.bou.readerforselfoss.themes.Toppings import apps.amine.bou.readerforselfoss.transformers.DepthPageTransformer @@ -71,7 +73,7 @@ class ReaderActivity : AppCompatActivity() { db = Room.databaseBuilder( applicationContext, AppDatabase::class.java, "selfoss-database" - ).addMigrations(MIGRATION_1_2).build() + ).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).build() val scoop = Scoop.getInstance() scoop.bind(this, Toppings.PRIMARY.value, toolBar) @@ -134,44 +136,50 @@ class ReaderActivity : AppCompatActivity() { } fun readItem(item: Item) { - if (this@ReaderActivity.isNetworkAccessible(this@ReaderActivity.findViewById(R.id.reader_activity_view)) && markOnScroll) { + if (markOnScroll) { thread { db.itemsDao().delete(item.toEntity()) } - api.markItem(item.id).enqueue( - object : Callback { - override fun onResponse( - call: Call, - response: Response - ) { - if (!response.succeeded() && debugReadingItems) { - val message = - "message: ${response.message()} " + - "response isSuccess: ${response.isSuccessful} " + - "response code: ${response.code()} " + - "response message: ${response.message()} " + - "response errorBody: ${response.errorBody()?.string()} " + - "body success: ${response.body()?.success} " + - "body isSuccess: ${response.body()?.isSuccess}" - ACRA.getErrorReporter() - .maybeHandleSilentException(Exception(message), this@ReaderActivity) + if (this@ReaderActivity.isNetworkAccessible(this@ReaderActivity.findViewById(R.id.reader_activity_view))) { + api.markItem(item.id).enqueue( + object : Callback { + override fun onResponse( + call: Call, + response: Response + ) { + if (!response.succeeded() && debugReadingItems) { + val message = + "message: ${response.message()} " + + "response isSuccess: ${response.isSuccessful} " + + "response code: ${response.code()} " + + "response message: ${response.message()} " + + "response errorBody: ${response.errorBody()?.string()} " + + "body success: ${response.body()?.success} " + + "body isSuccess: ${response.body()?.isSuccess}" + ACRA.getErrorReporter() + .maybeHandleSilentException(Exception(message), this@ReaderActivity) + } } - } - override fun onFailure( - call: Call, - t: Throwable - ) { - thread { - db.itemsDao().insertAllItems(item.toEntity()) - } - if (debugReadingItems) { - ACRA.getErrorReporter() - .maybeHandleSilentException(t, this@ReaderActivity) + override fun onFailure( + call: Call, + t: Throwable + ) { + thread { + db.itemsDao().insertAllItems(item.toEntity()) + } + if (debugReadingItems) { + ACRA.getErrorReporter() + .maybeHandleSilentException(t, this@ReaderActivity) + } } } + ) + } else { + thread { + db.actionsDao().insertAllActions(ActionEntity(item.id, true, false, false, false)) } - ) + } } } @@ -229,6 +237,19 @@ class ReaderActivity : AppCompatActivity() { } override fun onOptionsItemSelected(item: MenuItem): Boolean { + fun afterSave() { + allItems[pager.currentItem] = + allItems[pager.currentItem].toggleStar() + notifyAdapter() + canRemoveFromFavorite() + } + + fun afterUnsave() { + allItems[pager.currentItem] = allItems[pager.currentItem].toggleStar() + notifyAdapter() + canFavorite() + } + when (item.itemId) { android.R.id.home -> { onBackPressed() @@ -242,9 +263,7 @@ class ReaderActivity : AppCompatActivity() { call: Call, response: Response ) { - allItems[pager.currentItem] = allItems[pager.currentItem].toggleStar() - notifyAdapter() - canRemoveFromFavorite() + afterSave() } override fun onFailure( @@ -258,6 +277,11 @@ class ReaderActivity : AppCompatActivity() { ).show() } }) + } else { + thread { + db.actionsDao().insertAllActions(ActionEntity(allItems[pager.currentItem].id, false, false, true, false)) + afterSave() + } } } R.id.unsave -> { @@ -268,9 +292,7 @@ class ReaderActivity : AppCompatActivity() { call: Call, response: Response ) { - allItems[pager.currentItem] = allItems[pager.currentItem].toggleStar() - notifyAdapter() - canFavorite() + afterUnsave() } override fun onFailure( @@ -284,6 +306,11 @@ class ReaderActivity : AppCompatActivity() { ).show() } }) + } else { + thread { + db.actionsDao().insertAllActions(ActionEntity(allItems[pager.currentItem].id, false, false, false, true)) + afterUnsave() + } } } } diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/adapters/ItemCardAdapter.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/adapters/ItemCardAdapter.kt index b53be46..b45b9e6 100644 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/adapters/ItemCardAdapter.kt +++ b/app/src/main/java/apps/amine/bou/readerforselfoss/adapters/ItemCardAdapter.kt @@ -15,6 +15,7 @@ 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.persistence.database.AppDatabase +import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity import apps.amine.bou.readerforselfoss.themes.AppColors import apps.amine.bou.readerforselfoss.utils.LinkOnTouchListener import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent @@ -36,6 +37,7 @@ import kotlinx.android.synthetic.main.card_item.view.* import retrofit2.Call import retrofit2.Callback import retrofit2.Response +import kotlin.concurrent.thread class ItemCardAdapter( override val app: Activity, @@ -118,8 +120,8 @@ class ItemCardAdapter( mView.favButton.setOnLikeListener(object : OnLikeListener { override fun liked(likeButton: LikeButton) { + val (id) = items[adapterPosition] if (c.isNetworkAccessible(null)) { - val (id) = items[adapterPosition] api.starrItem(id).enqueue(object : Callback { override fun onResponse( call: Call, @@ -139,12 +141,16 @@ class ItemCardAdapter( ).show() } }) + } else { + thread { + db.actionsDao().insertAllActions(ActionEntity(id, false, false, true, false)) + } } } override fun unLiked(likeButton: LikeButton) { + val (id) = items[adapterPosition] if (c.isNetworkAccessible(null)) { - val (id) = items[adapterPosition] api.unstarrItem(id).enqueue(object : Callback { override fun onResponse( call: Call, @@ -164,6 +170,10 @@ class ItemCardAdapter( ).show() } }) + } else { + thread { + db.actionsDao().insertAllActions(ActionEntity(id, false, false, false, true)) + } } } }) diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/adapters/ItemsAdapter.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/adapters/ItemsAdapter.kt index 562f096..c322eb2 100644 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/adapters/ItemsAdapter.kt +++ b/app/src/main/java/apps/amine/bou/readerforselfoss/adapters/ItemsAdapter.kt @@ -11,6 +11,7 @@ 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.persistence.database.AppDatabase +import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity import apps.amine.bou.readerforselfoss.themes.AppColors import apps.amine.bou.readerforselfoss.utils.maybeHandleSilentException import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible @@ -46,14 +47,14 @@ abstract class ItemsAdapter : RecyclerView.Adapte Snackbar.LENGTH_LONG ) .setAction(R.string.undo_string) { - if (app.isNetworkAccessible(null)) { - items.add(position, i) - thread { - db.itemsDao().insertAllItems(i.toEntity()) - } - notifyItemInserted(position) - updateItems(items) + items.add(position, i) + thread { + db.itemsDao().insertAllItems(i.toEntity()) + } + notifyItemInserted(position) + updateItems(items) + if (app.isNetworkAccessible(null)) { api.unmarkItem(i.id).enqueue(object : Callback { override fun onResponse( call: Call, @@ -71,6 +72,10 @@ abstract class ItemsAdapter : RecyclerView.Adapte doUnmark(i, position) } }) + } else { + thread { + db.actionsDao().deleteReadActionForArticle(i.id) + } } } @@ -81,20 +86,16 @@ abstract class ItemsAdapter : RecyclerView.Adapte } fun removeItemAtIndex(position: Int) { + val i = items[position] + items.remove(i) + notifyItemRemoved(position) + updateItems(items) + + thread { + db.itemsDao().delete(i.toEntity()) + } + if (app.isNetworkAccessible(null)) { - val i = items[position] - - items.remove(i) - notifyItemRemoved(position) - updateItems(items) - - // TODO: Handle network status. - // IF offline, delete from cached articles, and add to some table that will replay the calls on network activation. - - thread { - db.itemsDao().delete(i.toEntity()) - } - api.markItem(i.id).enqueue(object : Callback { override fun onResponse( call: Call, @@ -135,6 +136,11 @@ abstract class ItemsAdapter : RecyclerView.Adapte } } }) + } else { + thread { + db.actionsDao().insertAllActions(ActionEntity(i.id, true, false, false, false)) + doUnmark(i, position) + } } } diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/background/background.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/background/background.kt index 60453e3..d96235e 100644 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/background/background.kt +++ b/app/src/main/java/apps/amine/bou/readerforselfoss/background/background.kt @@ -14,6 +14,7 @@ import apps.amine.bou.readerforselfoss.api.selfoss.Item import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi import apps.amine.bou.readerforselfoss.persistence.database.AppDatabase import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_1_2 +import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_2_3 import apps.amine.bou.readerforselfoss.utils.Config import apps.amine.bou.readerforselfoss.utils.persistence.toEntity import retrofit2.Call @@ -46,7 +47,7 @@ class LoadingWorker(val context: Context, params: WorkerParameters) : Worker(con val db = Room.databaseBuilder( applicationContext, AppDatabase::class.java, "selfoss-database" - ).addMigrations(MIGRATION_1_2).build() + ).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).build() val api = SelfossApi( this.context, diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/fragments/ArticleFragment.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/fragments/ArticleFragment.kt index 0e9c075..e8b7a65 100644 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/fragments/ArticleFragment.kt +++ b/app/src/main/java/apps/amine/bou/readerforselfoss/fragments/ArticleFragment.kt @@ -17,12 +17,17 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import android.webkit.WebSettings +import androidx.room.Room import apps.amine.bou.readerforselfoss.R import apps.amine.bou.readerforselfoss.api.mercury.MercuryApi import apps.amine.bou.readerforselfoss.api.mercury.ParsedContent 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.persistence.database.AppDatabase +import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity +import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_1_2 +import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_2_3 import apps.amine.bou.readerforselfoss.themes.AppColors import apps.amine.bou.readerforselfoss.utils.Config import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent @@ -45,6 +50,7 @@ import retrofit2.Callback import retrofit2.Response import java.net.MalformedURLException import java.net.URL +import kotlin.concurrent.thread class ArticleFragment : Fragment() { private lateinit var pageNumber: Number @@ -59,6 +65,7 @@ class ArticleFragment : Fragment() { private lateinit var editor: SharedPreferences.Editor private lateinit var fab: FloatingActionButton private lateinit var appColors: AppColors + private lateinit var db: AppDatabase override fun onStop() { super.onStop() @@ -72,6 +79,11 @@ class ArticleFragment : Fragment() { pageNumber = arguments!!.getInt(ARG_POSITION) allItems = arguments!!.getParcelableArrayList(ARG_ITEMS) + + db = Room.databaseBuilder( + context!!, + AppDatabase::class.java, "selfoss-database" + ).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).build() } private lateinit var rootView: ViewGroup @@ -166,6 +178,10 @@ class ArticleFragment : Fragment() { } } ) + } else { + thread { + db.actionsDao().insertAllActions(ActionEntity(allItems[pageNumber.toInt()].id, false, true, false, false)) + } } else -> Unit } diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/persistence/dao/ActionsDao.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/persistence/dao/ActionsDao.kt new file mode 100644 index 0000000..e18d452 --- /dev/null +++ b/app/src/main/java/apps/amine/bou/readerforselfoss/persistence/dao/ActionsDao.kt @@ -0,0 +1,23 @@ +package apps.amine.bou.readerforselfoss.persistence.dao + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity + +@Dao +interface ActionsDao { + @Query("SELECT * FROM actions order by id asc") + fun actions(): List + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insertAllActions(vararg actions: ActionEntity) + + @Query("DELETE FROM actions WHERE articleid = :article_id AND read = 1") + fun deleteReadActionForArticle(article_id: String) + + @Delete + fun delete(action: ActionEntity) +} \ No newline at end of file diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/persistence/database/AppDatabase.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/persistence/database/AppDatabase.kt index f177ef5..c9637fc 100644 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/persistence/database/AppDatabase.kt +++ b/app/src/main/java/apps/amine/bou/readerforselfoss/persistence/database/AppDatabase.kt @@ -2,15 +2,19 @@ package apps.amine.bou.readerforselfoss.persistence.database import androidx.room.RoomDatabase import androidx.room.Database +import apps.amine.bou.readerforselfoss.persistence.dao.ActionsDao import apps.amine.bou.readerforselfoss.persistence.dao.DrawerDataDao import apps.amine.bou.readerforselfoss.persistence.dao.ItemsDao +import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity import apps.amine.bou.readerforselfoss.persistence.entities.ItemEntity import apps.amine.bou.readerforselfoss.persistence.entities.SourceEntity import apps.amine.bou.readerforselfoss.persistence.entities.TagEntity -@Database(entities = [TagEntity::class, SourceEntity::class, ItemEntity::class], version = 2) +@Database(entities = [TagEntity::class, SourceEntity::class, ItemEntity::class, ActionEntity::class], version = 3) abstract class AppDatabase : RoomDatabase() { abstract fun drawerDataDao(): DrawerDataDao abstract fun itemsDao(): ItemsDao + + abstract fun actionsDao(): ActionsDao } \ No newline at end of file diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/persistence/entities/ActionEntity.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/persistence/entities/ActionEntity.kt new file mode 100644 index 0000000..9763a46 --- /dev/null +++ b/app/src/main/java/apps/amine/bou/readerforselfoss/persistence/entities/ActionEntity.kt @@ -0,0 +1,22 @@ +package apps.amine.bou.readerforselfoss.persistence.entities + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "actions") +data class ActionEntity( + @ColumnInfo(name = "articleid") + val articleId: String, + @ColumnInfo(name = "read") + val read: Boolean, + @ColumnInfo(name = "unread") + val unread: Boolean, + @ColumnInfo(name = "starred") + var starred: Boolean, + @ColumnInfo(name = "unstarred") + var unstarred: Boolean +) { + @PrimaryKey(autoGenerate = true) + var id: Int = 0 +} \ No newline at end of file diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/persistence/migrations/migrations.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/persistence/migrations/migrations.kt index f939fbf..f0f3376 100644 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/persistence/migrations/migrations.kt +++ b/app/src/main/java/apps/amine/bou/readerforselfoss/persistence/migrations/migrations.kt @@ -8,3 +8,9 @@ val MIGRATION_1_2: Migration = object : Migration(1, 2) { database.execSQL("CREATE TABLE IF NOT EXISTS `items` (`id` TEXT NOT NULL, `datetime` TEXT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `thumbnail` TEXT NOT NULL, `icon` TEXT NOT NULL, `link` TEXT NOT NULL, `sourcetitle` TEXT NOT NULL, `tags` TEXT NOT NULL, PRIMARY KEY(`id`))") } } + +val MIGRATION_2_3: Migration = object : Migration(2, 3) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("CREATE TABLE IF NOT EXISTS `actions` (`id` INTEGER NOT NULL, `articleid` TEXT NOT NULL, `read` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `unstarred` INTEGER NOT NULL, PRIMARY KEY(`id`))") + } +}