Compare commits
4 Commits
v171811307
...
v171811308
Author | SHA1 | Date | |
---|---|---|---|
aeeac8cccd | |||
7292edf997 | |||
f49256c72f | |||
d02b28b81f |
@ -1,5 +1,7 @@
|
||||
**1.7.x**
|
||||
|
||||
- Closes #179. Sync of read/unread/star/unstar items on background task or on app reload with network available.
|
||||
|
||||
- Closes #33. Background sync with settings.
|
||||
|
||||
- Closing #1. Initial article caching.
|
||||
|
@ -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"
|
||||
|
@ -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\")"
|
||||
]
|
||||
}
|
||||
}
|
@ -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<SuccessResponse> {
|
||||
override fun onResponse(
|
||||
call: Call<SuccessResponse>,
|
||||
@ -1428,5 +1432,39 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
|
||||
WorkManager.getInstance().enqueueUniquePeriodicWork("selfoss-loading", ExistingPeriodicWorkPolicy.KEEP, backgroundWork)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleOfflineActions() {
|
||||
fun <T>doAndReportOnFail(call: Call<T>, action: ActionEntity) {
|
||||
call.enqueue(object: Callback<T> {
|
||||
override fun onResponse(
|
||||
call: Call<T>,
|
||||
response: Response<T>
|
||||
) {
|
||||
thread {
|
||||
db.actionsDao().delete(action)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<T>, 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<SuccessResponse> {
|
||||
override fun onResponse(
|
||||
call: Call<SuccessResponse>,
|
||||
response: Response<SuccessResponse>
|
||||
) {
|
||||
if (!response.succeeded() && debugReadingItems) {
|
||||
val message =
|
||||
"message: ${response.message()} " +
|
||||
"response isSuccess: ${response.isSuccessful} " +
|
||||
"response code: ${response.code()} " +
|
||||
"response message: ${response.message()} " +
|
||||
"response errorBody: ${response.errorBody()?.string()} " +
|
||||
"body success: ${response.body()?.success} " +
|
||||
"body isSuccess: ${response.body()?.isSuccess}"
|
||||
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<SuccessResponse> {
|
||||
override fun onResponse(
|
||||
call: Call<SuccessResponse>,
|
||||
response: Response<SuccessResponse>
|
||||
) {
|
||||
if (!response.succeeded() && debugReadingItems) {
|
||||
val message =
|
||||
"message: ${response.message()} " +
|
||||
"response isSuccess: ${response.isSuccessful} " +
|
||||
"response code: ${response.code()} " +
|
||||
"response message: ${response.message()} " +
|
||||
"response errorBody: ${response.errorBody()?.string()} " +
|
||||
"body success: ${response.body()?.success} " +
|
||||
"body isSuccess: ${response.body()?.isSuccess}"
|
||||
ACRA.getErrorReporter()
|
||||
.maybeHandleSilentException(Exception(message), this@ReaderActivity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(
|
||||
call: Call<SuccessResponse>,
|
||||
t: Throwable
|
||||
) {
|
||||
thread {
|
||||
db.itemsDao().insertAllItems(item.toEntity())
|
||||
}
|
||||
if (debugReadingItems) {
|
||||
ACRA.getErrorReporter()
|
||||
.maybeHandleSilentException(t, this@ReaderActivity)
|
||||
override fun onFailure(
|
||||
call: Call<SuccessResponse>,
|
||||
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<SuccessResponse>,
|
||||
response: Response<SuccessResponse>
|
||||
) {
|
||||
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<SuccessResponse>,
|
||||
response: Response<SuccessResponse>
|
||||
) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<SuccessResponse> {
|
||||
override fun onResponse(
|
||||
call: Call<SuccessResponse>,
|
||||
@ -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<SuccessResponse> {
|
||||
override fun onResponse(
|
||||
call: Call<SuccessResponse>,
|
||||
@ -164,6 +170,10 @@ class ItemCardAdapter(
|
||||
).show()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
thread {
|
||||
db.actionsDao().insertAllActions(ActionEntity(id, false, false, false, true))
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -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<VH : RecyclerView.ViewHolder?> : 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<SuccessResponse> {
|
||||
override fun onResponse(
|
||||
call: Call<SuccessResponse>,
|
||||
@ -71,6 +72,10 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
|
||||
doUnmark(i, position)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
thread {
|
||||
db.actionsDao().deleteReadActionForArticle(i.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -81,20 +86,16 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : 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<SuccessResponse> {
|
||||
override fun onResponse(
|
||||
call: Call<SuccessResponse>,
|
||||
@ -135,6 +136,11 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
thread {
|
||||
db.actionsDao().insertAllActions(ActionEntity(i.id, true, false, false, false))
|
||||
doUnmark(i, position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,9 +13,14 @@ import apps.amine.bou.readerforselfoss.R
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.Item
|
||||
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
|
||||
import apps.amine.bou.readerforselfoss.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.utils.Config
|
||||
import apps.amine.bou.readerforselfoss.utils.maybeHandleSilentException
|
||||
import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible
|
||||
import apps.amine.bou.readerforselfoss.utils.persistence.toEntity
|
||||
import org.acra.ACRA
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
@ -24,60 +29,98 @@ import kotlin.concurrent.schedule
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
class LoadingWorker(val context: Context, params: WorkerParameters) : Worker(context, params) {
|
||||
lateinit var db: AppDatabase
|
||||
|
||||
override fun doWork(): Result {
|
||||
val notificationManager =
|
||||
applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
if (context.isNetworkAccessible(null)) {
|
||||
|
||||
val notification = NotificationCompat.Builder(applicationContext, Config.syncChannelId)
|
||||
.setContentTitle(context.getString(R.string.loading_notification_title))
|
||||
.setContentText(context.getString(R.string.loading_notification_text))
|
||||
.setOngoing(true)
|
||||
.setPriority(PRIORITY_LOW)
|
||||
.setChannelId(Config.syncChannelId)
|
||||
.setSmallIcon(R.drawable.ic_cloud_download)
|
||||
val notificationManager =
|
||||
applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
notificationManager.notify(1, notification.build())
|
||||
val notification = NotificationCompat.Builder(applicationContext, Config.syncChannelId)
|
||||
.setContentTitle(context.getString(R.string.loading_notification_title))
|
||||
.setContentText(context.getString(R.string.loading_notification_text))
|
||||
.setOngoing(true)
|
||||
.setPriority(PRIORITY_LOW)
|
||||
.setChannelId(Config.syncChannelId)
|
||||
.setSmallIcon(R.drawable.ic_cloud_download)
|
||||
|
||||
val settings = this.context.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
|
||||
val sharedPref = PreferenceManager.getDefaultSharedPreferences(this.context)
|
||||
val shouldLogEverything = sharedPref.getBoolean("should_log_everything", false)
|
||||
notificationManager.notify(1, notification.build())
|
||||
|
||||
val db = Room.databaseBuilder(
|
||||
applicationContext,
|
||||
AppDatabase::class.java, "selfoss-database"
|
||||
).addMigrations(MIGRATION_1_2).build()
|
||||
val settings =
|
||||
this.context.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
|
||||
val sharedPref = PreferenceManager.getDefaultSharedPreferences(this.context)
|
||||
val shouldLogEverything = sharedPref.getBoolean("should_log_everything", false)
|
||||
|
||||
val api = SelfossApi(
|
||||
this.context,
|
||||
null,
|
||||
settings.getBoolean("isSelfSignedCert", false),
|
||||
shouldLogEverything
|
||||
)
|
||||
api.allItems().enqueue(object : Callback<List<Item>> {
|
||||
override fun onFailure(call: Call<List<Item>>, t: Throwable) {
|
||||
Timer("", false).schedule(4000) {
|
||||
notificationManager.cancel(1)
|
||||
}
|
||||
}
|
||||
db = Room.databaseBuilder(
|
||||
applicationContext,
|
||||
AppDatabase::class.java, "selfoss-database"
|
||||
).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).build()
|
||||
|
||||
override fun onResponse(
|
||||
call: Call<List<Item>>,
|
||||
response: Response<List<Item>>
|
||||
) {
|
||||
thread {
|
||||
if (response.body() != null) {
|
||||
val apiItems = (response.body() as ArrayList<Item>)
|
||||
db.itemsDao().deleteAllItems()
|
||||
db.itemsDao()
|
||||
.insertAllItems(*(apiItems.map { it.toEntity() }).toTypedArray())
|
||||
}
|
||||
val api = SelfossApi(
|
||||
this.context,
|
||||
null,
|
||||
settings.getBoolean("isSelfSignedCert", false),
|
||||
shouldLogEverything
|
||||
)
|
||||
|
||||
api.allItems().enqueue(object : Callback<List<Item>> {
|
||||
override fun onFailure(call: Call<List<Item>>, t: Throwable) {
|
||||
Timer("", false).schedule(4000) {
|
||||
notificationManager.cancel(1)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResponse(
|
||||
call: Call<List<Item>>,
|
||||
response: Response<List<Item>>
|
||||
) {
|
||||
thread {
|
||||
if (response.body() != null) {
|
||||
val apiItems = (response.body() as ArrayList<Item>)
|
||||
db.itemsDao().deleteAllItems()
|
||||
db.itemsDao()
|
||||
.insertAllItems(*(apiItems.map { it.toEntity() }).toTypedArray())
|
||||
}
|
||||
Timer("", false).schedule(4000) {
|
||||
notificationManager.cancel(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
return Result.SUCCESS
|
||||
}
|
||||
|
||||
private fun <T> doAndReportOnFail(call: Call<T>, action: ActionEntity) {
|
||||
call.enqueue(object : Callback<T> {
|
||||
override fun onResponse(
|
||||
call: Call<T>,
|
||||
response: Response<T>
|
||||
) {
|
||||
thread {
|
||||
db.actionsDao().delete(action)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<T>, t: Throwable) {
|
||||
ACRA.getErrorReporter().maybeHandleSilentException(t, context)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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<ActionEntity>
|
||||
|
||||
@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)
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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`))")
|
||||
}
|
||||
}
|
||||
|
@ -23,10 +23,9 @@
|
||||
android:singleLine="true"
|
||||
android:title="@string/pref_periodic_refresh_minutes_title" />
|
||||
|
||||
|
||||
<SwitchPreference
|
||||
android:defaultValue="false"
|
||||
android:key="refresh_when_charging"
|
||||
android:dependency="items_caching"
|
||||
android:dependency="periodic_refresh"
|
||||
android:title="@string/pref_switch_refresh_when_charging" />
|
||||
</PreferenceScreen>
|
||||
|
Reference in New Issue
Block a user