Replacing room with sqldelight. Big cleaning.

This commit is contained in:
aminecmi 2022-08-23 15:12:01 +02:00
parent afcc55e907
commit 495b101355
43 changed files with 393 additions and 1488 deletions

View File

@ -78,12 +78,6 @@ android {
// tests // tests
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
javaCompileOptions {
annotationProcessorOptions {
arguments += mapOf("room.schemaLocation" to "$projectDir/schemas")
}
}
} }
buildTypes { buildTypes {
getByName("release") { getByName("release") {
@ -201,11 +195,11 @@ dependencies {
implementation("androidx.lifecycle:lifecycle-runtime:2.5.1") implementation("androidx.lifecycle:lifecycle-runtime:2.5.1")
implementation("androidx.lifecycle:lifecycle-extensions:2.2.0") implementation("androidx.lifecycle:lifecycle-extensions:2.2.0")
implementation("androidx.room:room-ktx:2.4.0-beta01")
kapt("androidx.room:room-compiler:2.4.0-beta01")
implementation("android.arch.work:work-runtime-ktx:1.0.1") implementation("android.arch.work:work-runtime-ktx:1.0.1")
// Network information // Network information
implementation("com.github.ln-12:multiplatform-connectivity-status:1.3.0") implementation("com.github.ln-12:multiplatform-connectivity-status:1.3.0")
// SQLDELIGHT
implementation("com.squareup.sqldelight:android-driver:1.5.3")
} }

View File

@ -1,96 +0,0 @@
{
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "08ca537d7ac9d4dd216e8e395d70801a",
"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": []
}
],
"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, \"08ca537d7ac9d4dd216e8e395d70801a\")"
]
}
}

View File

@ -1,176 +0,0 @@
{
"formatVersion": 1,
"database": {
"version": 2,
"identityHash": "6fa6944b04100d68eab61039876a8804",
"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": []
}
],
"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, \"6fa6944b04100d68eab61039876a8804\")"
]
}
}

View File

@ -1,226 +0,0 @@
{
"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\")"
]
}
}

View File

@ -1,226 +0,0 @@
{
"formatVersion": 1,
"database": {
"version": 4,
"identityHash": "9cf8b03d32f80dfd58160599a1df197d",
"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, `icon` TEXT, `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": false
},
{
"fieldPath": "icon",
"columnName": "icon",
"affinity": "TEXT",
"notNull": false
},
{
"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, \"9cf8b03d32f80dfd58160599a1df197d\")"
]
}
}

View File

@ -1,226 +0,0 @@
{
"formatVersion": 1,
"database": {
"version": 4,
"identityHash": "9cf8b03d32f80dfd58160599a1df197d",
"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, `icon` TEXT, `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": false
},
{
"fieldPath": "icon",
"columnName": "icon",
"affinity": "TEXT",
"notNull": false
},
{
"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}` (`articleid` TEXT NOT NULL, `read` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `unstarred` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
"fields": [
{
"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
},
{
"fieldPath": "id",
"columnName": "id",
"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, '9cf8b03d32f80dfd58160599a1df197d')"
]
}
}

View File

@ -18,7 +18,6 @@ import androidx.appcompat.widget.SearchView
import androidx.core.view.doOnNextLayout import androidx.core.view.doOnNextLayout
import androidx.drawerlayout.widget.DrawerLayout import androidx.drawerlayout.widget.DrawerLayout
import androidx.recyclerview.widget.* import androidx.recyclerview.widget.*
import androidx.room.Room
import androidx.work.Constraints import androidx.work.Constraints
import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.PeriodicWorkRequestBuilder import androidx.work.PeriodicWorkRequestBuilder
@ -30,13 +29,6 @@ import bou.amine.apps.readerforselfossv2.android.background.LoadingWorker
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityHomeBinding import bou.amine.apps.readerforselfossv2.android.databinding.ActivityHomeBinding
import bou.amine.apps.readerforselfossv2.android.model.getIcon import bou.amine.apps.readerforselfossv2.android.model.getIcon
import bou.amine.apps.readerforselfossv2.android.model.getTitleDecoded import bou.amine.apps.readerforselfossv2.android.model.getTitleDecoded
import bou.amine.apps.readerforselfossv2.android.persistence.AndroidDeviceDatabase
import bou.amine.apps.readerforselfossv2.android.persistence.AndroidDeviceDatabaseService
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
import bou.amine.apps.readerforselfossv2.android.persistence.entities.ActionEntity
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_1_2
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_2_3
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_3_4
import bou.amine.apps.readerforselfossv2.android.settings.SettingsActivity import bou.amine.apps.readerforselfossv2.android.settings.SettingsActivity
import bou.amine.apps.readerforselfossv2.android.themes.AppColors import bou.amine.apps.readerforselfossv2.android.themes.AppColors
import bou.amine.apps.readerforselfossv2.android.themes.Toppings import bou.amine.apps.readerforselfossv2.android.themes.Toppings
@ -44,13 +36,13 @@ import bou.amine.apps.readerforselfossv2.android.utils.Config
import bou.amine.apps.readerforselfossv2.android.utils.bottombar.maybeShow import bou.amine.apps.readerforselfossv2.android.utils.bottombar.maybeShow
import bou.amine.apps.readerforselfossv2.android.utils.bottombar.removeBadge import bou.amine.apps.readerforselfossv2.android.utils.bottombar.removeBadge
import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper
import bou.amine.apps.readerforselfossv2.android.utils.persistence.toEntity import bou.amine.apps.readerforselfossv2.dao.ACTION
import bou.amine.apps.readerforselfossv2.android.utils.persistence.toView
import bou.amine.apps.readerforselfossv2.android.viewmodel.AppViewModel
import bou.amine.apps.readerforselfossv2.repository.Repository import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.rest.SelfossModel import bou.amine.apps.readerforselfossv2.rest.SelfossModel
import bou.amine.apps.readerforselfossv2.utils.ItemType import bou.amine.apps.readerforselfossv2.utils.ItemType
import bou.amine.apps.readerforselfossv2.utils.longHash import bou.amine.apps.readerforselfossv2.utils.longHash
import bou.amine.apps.readerforselfossv2.utils.toEntity
import bou.amine.apps.readerforselfossv2.utils.toView
import com.ashokvarma.bottomnavigation.BottomNavigationBar import com.ashokvarma.bottomnavigation.BottomNavigationBar
import com.ashokvarma.bottomnavigation.BottomNavigationItem import com.ashokvarma.bottomnavigation.BottomNavigationItem
import com.ashokvarma.bottomnavigation.TextBadgeItem import com.ashokvarma.bottomnavigation.TextBadgeItem
@ -83,8 +75,6 @@ import kotlin.concurrent.thread
class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAware { class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAware {
private lateinit var dataBase: AndroidDeviceDatabase
private lateinit var dbService: AndroidDeviceDatabaseService
private val MENU_PREFERENCES = 12302 private val MENU_PREFERENCES = 12302
private val DRAWER_ID_TAGS = 100101L private val DRAWER_ID_TAGS = 100101L
private val DRAWER_ID_HIDDEN_TAGS = 101100L private val DRAWER_ID_HIDDEN_TAGS = 101100L
@ -129,8 +119,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
private lateinit var tagsBadge: Map<Long, Int> private lateinit var tagsBadge: Map<Long, Int>
private lateinit var db: AppDatabase
private lateinit var config: Config private lateinit var config: Config
override val di by closestDI() override val di by closestDI()
@ -169,15 +157,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
binding.drawerContainer.addDrawerListener(mDrawerToggle) binding.drawerContainer.addDrawerListener(mDrawerToggle)
mDrawerToggle.syncState() mDrawerToggle.syncState()
db = Room.databaseBuilder(
applicationContext,
AppDatabase::class.java, "selfoss-database"
).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).addMigrations(MIGRATION_3_4).build()
customTabActivityHelper = CustomTabActivityHelper() customTabActivityHelper = CustomTabActivityHelper()
dataBase = AndroidDeviceDatabase(applicationContext)
handleBottomBar() handleBottomBar()
handleDrawer() handleDrawer()
@ -466,6 +447,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
} }
} }
// TODO: refactor this.
private fun handleDrawerItems() { private fun handleDrawerItems() {
tagsBadge = emptyMap() tagsBadge = emptyMap()
fun handleDrawerData(maybeDrawerData: DrawerData?, loadedFromCache: Boolean = false) { fun handleDrawerData(maybeDrawerData: DrawerData?, loadedFromCache: Boolean = false) {
@ -650,17 +632,12 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
if (!loadedFromCache) { if (!loadedFromCache) {
if (maybeDrawerData.tags != null) { if (maybeDrawerData.tags != null) {
thread { thread {
val tagEntities = maybeDrawerData.tags.map { it.toEntity() } repository.resetDBTagsWithData(maybeDrawerData.tags)
db.drawerDataDao().deleteAllTags()
db.drawerDataDao().insertAllTags(*tagEntities.toTypedArray())
} }
} }
if (maybeDrawerData.sources != null) { if (maybeDrawerData.sources != null) {
thread { thread {
val sourceEntities = repository.resetDBSourcesWithData(maybeDrawerData.sources)
maybeDrawerData.sources.map { it.toEntity() }
db.drawerDataDao().deleteAllSources()
db.drawerDataDao().insertAllSources(*sourceEntities.toTypedArray())
} }
} }
} }
@ -718,8 +695,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
) )
thread { thread {
val drawerData = DrawerData(db.drawerDataDao().tags().map { it.toView() }, val drawerData = DrawerData(repository.getDBTags().map { it.toView() },
db.drawerDataDao().sources().map { it.toView() }) repository.getDBSources().map { it.toView() })
runOnUiThread { runOnUiThread {
handleDrawerData(drawerData, loadedFromCache = true) handleDrawerData(drawerData, loadedFromCache = true)
drawerApiCalls(drawerData) drawerApiCalls(drawerData)
@ -894,7 +871,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
ItemCardAdapter( ItemCardAdapter(
this, this,
items, items,
db,
customTabActivityHelper, customTabActivityHelper,
internalBrowser, internalBrowser,
articleViewer, articleViewer,
@ -909,7 +885,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
ItemListAdapter( ItemListAdapter(
this, this,
items, items,
db,
customTabActivityHelper, customTabActivityHelper,
internalBrowser, internalBrowser,
articleViewer, articleViewer,
@ -1106,23 +1081,23 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
} }
private fun handleOfflineActions() { private fun handleOfflineActions() {
fun doAndReportOnFail(success: Boolean, action: ActionEntity) { fun doAndReportOnFail(success: Boolean, action: ACTION) {
if (success) { if (success) {
thread { thread {
db.actionsDao().delete(action) repository.deleteDBAction(action)
} }
} }
} }
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
val actions = db.actionsDao().actions() val actions = repository.getDBActions()
actions.forEach { action -> actions.forEach { action ->
when { when {
action.read -> doAndReportOnFail(repository.markAsReadById(action.articleId.toInt()), action) action.read -> doAndReportOnFail(repository.markAsReadById(action.articleid.toInt()), action)
action.unread -> doAndReportOnFail(repository.unmarkAsReadById(action.articleId.toInt()), action) action.unread -> doAndReportOnFail(repository.unmarkAsReadById(action.articleid.toInt()), action)
action.starred -> doAndReportOnFail(repository.starrById(action.articleId.toInt()), action) action.starred -> doAndReportOnFail(repository.starrById(action.articleid.toInt()), action)
action.unstarred -> doAndReportOnFail(repository.unstarrById(action.articleId.toInt()), action) action.unstarred -> doAndReportOnFail(repository.unstarrById(action.articleid.toInt()), action)
} }
} }
} }

View File

@ -11,13 +11,14 @@ import android.widget.Toast
import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner import androidx.lifecycle.ProcessLifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.multidex.MultiDexApplication import androidx.multidex.MultiDexApplication
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import bou.amine.apps.readerforselfossv2.DI.networkModule import bou.amine.apps.readerforselfossv2.DI.networkModule
import bou.amine.apps.readerforselfossv2.android.utils.Config import bou.amine.apps.readerforselfossv2.android.utils.Config
import bou.amine.apps.readerforselfossv2.android.utils.glide.loadMaybeBasicAuth import bou.amine.apps.readerforselfossv2.android.utils.glide.loadMaybeBasicAuth
import bou.amine.apps.readerforselfossv2.android.viewmodel.AppViewModel import bou.amine.apps.readerforselfossv2.android.viewmodel.AppViewModel
import bou.amine.apps.readerforselfossv2.dao.DriverFactory
import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB
import bou.amine.apps.readerforselfossv2.repository.Repository import bou.amine.apps.readerforselfossv2.repository.Repository
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.RequestOptions
@ -26,18 +27,20 @@ import com.github.ln_12.library.ConnectivityStatus
import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader
import com.mikepenz.materialdrawer.util.DrawerImageLoader import com.mikepenz.materialdrawer.util.DrawerImageLoader
import com.russhwolf.settings.Settings import com.russhwolf.settings.Settings
import io.github.aakira.napier.DebugAntilog
import io.github.aakira.napier.Napier
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import io.github.aakira.napier.DebugAntilog
import io.github.aakira.napier.Napier
import org.kodein.di.* import org.kodein.di.*
class MyApp : MultiDexApplication(), DIAware { class MyApp : MultiDexApplication(), DIAware {
override val di by DI.lazy { override val di by DI.lazy {
import(networkModule) import(networkModule)
bind<Repository>() with singleton { Repository(instance(), instance(), connectivityStatus) } bind<DriverFactory>() with singleton { DriverFactory(applicationContext) }
bind<ReaderForSelfossDB>() with singleton { ReaderForSelfossDB(driverFactory.createDriver()) }
bind<Repository>() with singleton { Repository(instance(), instance(), connectivityStatus, instance()) }
bind<ConnectivityStatus>() with singleton { ConnectivityStatus(applicationContext) } bind<ConnectivityStatus>() with singleton { ConnectivityStatus(applicationContext) }
bind<AppViewModel>() with singleton { AppViewModel(repository = instance()) } bind<AppViewModel>() with singleton { AppViewModel(repository = instance()) }
} }
@ -45,6 +48,7 @@ class MyApp : MultiDexApplication(), DIAware {
private val repository: Repository by instance() private val repository: Repository by instance()
private val viewModel: AppViewModel by instance() private val viewModel: AppViewModel by instance()
private val connectivityStatus: ConnectivityStatus by instance() private val connectivityStatus: ConnectivityStatus by instance()
private val driverFactory: DriverFactory by instance()
private lateinit var config: Config private lateinit var config: Config
private lateinit var settings : Settings private lateinit var settings : Settings
@ -73,6 +77,7 @@ class MyApp : MultiDexApplication(), DIAware {
).show() ).show()
} }
} }
} }
private fun handleNotificationChannels() { private fun handleNotificationChannels() {

View File

@ -8,15 +8,10 @@ import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.room.Room
import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2 import androidx.viewpager2.widget.ViewPager2
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityReaderBinding import bou.amine.apps.readerforselfossv2.android.databinding.ActivityReaderBinding
import bou.amine.apps.readerforselfossv2.android.fragments.ArticleFragment import bou.amine.apps.readerforselfossv2.android.fragments.ArticleFragment
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_1_2
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_2_3
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_3_4
import bou.amine.apps.readerforselfossv2.android.themes.AppColors import bou.amine.apps.readerforselfossv2.android.themes.AppColors
import bou.amine.apps.readerforselfossv2.android.themes.Toppings import bou.amine.apps.readerforselfossv2.android.themes.Toppings
import bou.amine.apps.readerforselfossv2.android.utils.toggleStar import bou.amine.apps.readerforselfossv2.android.utils.toggleStar
@ -39,7 +34,6 @@ class ReaderActivity : AppCompatActivity(), DIAware {
private lateinit var toolbarMenu: Menu private lateinit var toolbarMenu: Menu
private lateinit var db: AppDatabase
private lateinit var binding: ActivityReaderBinding private lateinit var binding: ActivityReaderBinding
private var activeAlignment: Int = 1 private var activeAlignment: Int = 1
@ -47,7 +41,7 @@ class ReaderActivity : AppCompatActivity(), DIAware {
private val ALIGN_LEFT = 2 private val ALIGN_LEFT = 2
override val di by closestDI() override val di by closestDI()
private val repository : Repository by instance() private val repository: Repository by instance()
private fun showMenuItem(willAddToFavorite: Boolean) { private fun showMenuItem(willAddToFavorite: Boolean) {
if (willAddToFavorite) { if (willAddToFavorite) {
@ -75,11 +69,6 @@ class ReaderActivity : AppCompatActivity(), DIAware {
setContentView(view) setContentView(view)
db = Room.databaseBuilder(
applicationContext,
AppDatabase::class.java, "selfoss-database"
).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).addMigrations(MIGRATION_3_4).build()
val scoop = Scoop.getInstance() val scoop = Scoop.getInstance()
scoop.bind(this, Toppings.PRIMARY.value, binding.toolBar) scoop.bind(this, Toppings.PRIMARY.value, binding.toolBar)
scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.value) scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.value)
@ -111,11 +100,11 @@ class ReaderActivity : AppCompatActivity(), DIAware {
private fun readItem(item: SelfossModel.Item) { private fun readItem(item: SelfossModel.Item) {
if (markOnScroll) { if (markOnScroll) {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
repository.markAsRead(item) repository.markAsRead(item)
// TODO: Handle failure // TODO: Handle failure
}
} }
}
} }
override fun onSaveInstanceState(oldInstanceState: Bundle) { override fun onSaveInstanceState(oldInstanceState: Bundle) {
@ -128,19 +117,22 @@ class ReaderActivity : AppCompatActivity(), DIAware {
override fun getItemCount(): Int = allItems.size override fun getItemCount(): Int = allItems.size
override fun createFragment(position: Int): Fragment = ArticleFragment.newInstance(allItems[position]) override fun createFragment(position: Int): Fragment =
ArticleFragment.newInstance(allItems[position])
} }
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
return when (keyCode) { return when (keyCode) {
KeyEvent.KEYCODE_VOLUME_DOWN -> { KeyEvent.KEYCODE_VOLUME_DOWN -> {
val currentFragment = supportFragmentManager.findFragmentByTag("f" + binding.pager.currentItem) as ArticleFragment val currentFragment =
supportFragmentManager.findFragmentByTag("f" + binding.pager.currentItem) as ArticleFragment
currentFragment.scrollDown() currentFragment.scrollDown()
true true
} }
KeyEvent.KEYCODE_VOLUME_UP -> { KeyEvent.KEYCODE_VOLUME_UP -> {
val currentFragment = supportFragmentManager.findFragmentByTag("f" + binding.pager.currentItem) as ArticleFragment val currentFragment =
supportFragmentManager.findFragmentByTag("f" + binding.pager.currentItem) as ArticleFragment
currentFragment.scrollUp() currentFragment.scrollUp()
true true
} }
@ -169,19 +161,19 @@ class ReaderActivity : AppCompatActivity(), DIAware {
alignmentMenu() alignmentMenu()
binding.pager.registerOnPageChangeCallback( binding.pager.registerOnPageChangeCallback(
object : ViewPager2.OnPageChangeCallback() { object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) { override fun onPageSelected(position: Int) {
super.onPageSelected(position) super.onPageSelected(position)
if (allItems[position].starred) { if (allItems[position].starred) {
canRemoveFromFavorite() canRemoveFromFavorite()
} else { } else {
canFavorite() canFavorite()
}
readItem(allItems[position])
} }
readItem(allItems[position])
} }
}
) )
return true return true
@ -190,7 +182,7 @@ class ReaderActivity : AppCompatActivity(), DIAware {
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
fun afterSave() { fun afterSave() {
allItems[binding.pager.currentItem] = allItems[binding.pager.currentItem] =
allItems[binding.pager.currentItem].toggleStar() allItems[binding.pager.currentItem].toggleStar()
canRemoveFromFavorite() canRemoveFromFavorite()
} }

View File

@ -10,7 +10,6 @@ import androidx.recyclerview.widget.RecyclerView
import bou.amine.apps.readerforselfossv2.android.R import bou.amine.apps.readerforselfossv2.android.R
import bou.amine.apps.readerforselfossv2.android.databinding.CardItemBinding import bou.amine.apps.readerforselfossv2.android.databinding.CardItemBinding
import bou.amine.apps.readerforselfossv2.android.model.* import bou.amine.apps.readerforselfossv2.android.model.*
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
import bou.amine.apps.readerforselfossv2.android.themes.AppColors import bou.amine.apps.readerforselfossv2.android.themes.AppColors
import bou.amine.apps.readerforselfossv2.android.utils.* import bou.amine.apps.readerforselfossv2.android.utils.*
import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper
@ -31,7 +30,6 @@ import org.kodein.di.instance
class ItemCardAdapter( class ItemCardAdapter(
override val app: Activity, override val app: Activity,
override var items: ArrayList<SelfossModel.Item>, override var items: ArrayList<SelfossModel.Item>,
override val db: AppDatabase,
private val helper: CustomTabActivityHelper, private val helper: CustomTabActivityHelper,
private val internalBrowser: Boolean, private val internalBrowser: Boolean,
private val articleViewer: Boolean, private val articleViewer: Boolean,

View File

@ -7,7 +7,6 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import bou.amine.apps.readerforselfossv2.android.databinding.ListItemBinding import bou.amine.apps.readerforselfossv2.android.databinding.ListItemBinding
import bou.amine.apps.readerforselfossv2.android.model.* import bou.amine.apps.readerforselfossv2.android.model.*
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
import bou.amine.apps.readerforselfossv2.android.themes.AppColors import bou.amine.apps.readerforselfossv2.android.themes.AppColors
import bou.amine.apps.readerforselfossv2.android.utils.* import bou.amine.apps.readerforselfossv2.android.utils.*
import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper
@ -24,7 +23,6 @@ import org.kodein.di.instance
class ItemListAdapter( class ItemListAdapter(
override val app: Activity, override val app: Activity,
override var items: ArrayList<SelfossModel.Item>, override var items: ArrayList<SelfossModel.Item>,
override val db: AppDatabase,
private val helper: CustomTabActivityHelper, private val helper: CustomTabActivityHelper,
private val internalBrowser: Boolean, private val internalBrowser: Boolean,
private val articleViewer: Boolean, private val articleViewer: Boolean,

View File

@ -5,7 +5,6 @@ import android.graphics.Color
import android.widget.TextView import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import bou.amine.apps.readerforselfossv2.android.R import bou.amine.apps.readerforselfossv2.android.R
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
import bou.amine.apps.readerforselfossv2.android.themes.AppColors import bou.amine.apps.readerforselfossv2.android.themes.AppColors
import bou.amine.apps.readerforselfossv2.android.utils.Config import bou.amine.apps.readerforselfossv2.android.utils.Config
import bou.amine.apps.readerforselfossv2.repository.Repository import bou.amine.apps.readerforselfossv2.repository.Repository
@ -20,7 +19,6 @@ import org.kodein.di.DIAware
abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapter<VH>(), DIAware { abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapter<VH>(), DIAware {
abstract var items: ArrayList<SelfossModel.Item> abstract var items: ArrayList<SelfossModel.Item>
abstract val repository: Repository abstract val repository: Repository
abstract val db: AppDatabase
abstract val app: Activity abstract val app: Activity
abstract val appColors: AppColors abstract val appColors: AppColors
abstract val config: Config abstract val config: Config

View File

@ -8,20 +8,15 @@ import android.os.Build
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationCompat.PRIORITY_DEFAULT import androidx.core.app.NotificationCompat.PRIORITY_DEFAULT
import androidx.core.app.NotificationCompat.PRIORITY_LOW import androidx.core.app.NotificationCompat.PRIORITY_LOW
import androidx.room.Room
import androidx.work.Worker import androidx.work.Worker
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import bou.amine.apps.readerforselfossv2.android.MainActivity import bou.amine.apps.readerforselfossv2.android.MainActivity
import bou.amine.apps.readerforselfossv2.android.MyApp import bou.amine.apps.readerforselfossv2.android.MyApp
import bou.amine.apps.readerforselfossv2.android.R import bou.amine.apps.readerforselfossv2.android.R
import bou.amine.apps.readerforselfossv2.android.model.preloadImages import bou.amine.apps.readerforselfossv2.android.model.preloadImages
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
import bou.amine.apps.readerforselfossv2.android.persistence.entities.ActionEntity
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_1_2
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_2_3
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_3_4
import bou.amine.apps.readerforselfossv2.android.utils.Config import bou.amine.apps.readerforselfossv2.android.utils.Config
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAccessible import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAccessible
import bou.amine.apps.readerforselfossv2.dao.ACTION
import bou.amine.apps.readerforselfossv2.repository.Repository import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.rest.SelfossModel import bou.amine.apps.readerforselfossv2.rest.SelfossModel
import bou.amine.apps.readerforselfossv2.utils.ItemType import bou.amine.apps.readerforselfossv2.utils.ItemType
@ -36,7 +31,6 @@ import kotlin.concurrent.schedule
import kotlin.concurrent.thread import kotlin.concurrent.thread
class LoadingWorker(val context: Context, params: WorkerParameters) : Worker(context, params), DIAware { class LoadingWorker(val context: Context, params: WorkerParameters) : Worker(context, params), DIAware {
lateinit var db: AppDatabase
override val di by lazy { (applicationContext as MyApp).di } override val di by lazy { (applicationContext as MyApp).di }
private val repository : Repository by instance() private val repository : Repository by instance()
@ -63,30 +57,24 @@ override fun doWork(): Result {
val notifyNewItems = settings.getBoolean("notify_new_items", false) val notifyNewItems = settings.getBoolean("notify_new_items", false)
db = Room.databaseBuilder( val actions: List<ACTION> = repository.getDBActions()
applicationContext,
AppDatabase::class.java, "selfoss-database"
).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3)
.addMigrations(MIGRATION_3_4).build()
val actions = db.actionsDao().actions()
actions.forEach { action -> actions.forEach { action ->
when { when {
action.read -> doAndReportOnFail( action.read -> doAndReportOnFail(
repository.markAsReadById(action.articleId.toInt()), repository.markAsReadById(action.articleid.toInt()),
action action
) )
action.unread -> doAndReportOnFail( action.unread -> doAndReportOnFail(
repository.unmarkAsReadById(action.articleId.toInt()), repository.unmarkAsReadById(action.articleid.toInt()),
action action
) )
action.starred -> doAndReportOnFail( action.starred -> doAndReportOnFail(
repository.starrById(action.articleId.toInt()), repository.starrById(action.articleid.toInt()),
action action
) )
action.unstarred -> doAndReportOnFail( action.unstarred -> doAndReportOnFail(
repository.unstarrById(action.articleId.toInt()), repository.unstarrById(action.articleid.toInt()),
action action
) )
} }
@ -96,9 +84,8 @@ override fun doWork(): Result {
try { try {
val newItems = repository.allItems(ItemType.UNREAD) val newItems = repository.allItems(ItemType.UNREAD)
handleNewItemsNotification(newItems, notifyNewItems, notificationManager) handleNewItemsNotification(newItems, notifyNewItems, notificationManager)
val readItems = repository.allItems(ItemType.ALL) repository.allItems(ItemType.ALL)
val starredItems = repository.allItems(ItemType.STARRED) repository.allItems(ItemType.STARRED)
// TODO: save all to DB
} catch (e: Throwable) {} } catch (e: Throwable) {}
} }
} }
@ -154,11 +141,10 @@ override fun doWork(): Result {
} }
} }
private fun doAndReportOnFail(result: Boolean, action: ActionEntity) { private fun doAndReportOnFail(result: Boolean, action: ACTION) {
// TODO: Failures should be reported
if (result) { if (result) {
thread { thread {
db.actionsDao().delete(action) repository.deleteDBAction(action)
} }
} }
} }

View File

@ -19,17 +19,12 @@ import androidx.browser.customtabs.CustomTabsIntent
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.core.widget.NestedScrollView import androidx.core.widget.NestedScrollView
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.room.Room
import bou.amine.apps.readerforselfossv2.android.ImageActivity import bou.amine.apps.readerforselfossv2.android.ImageActivity
import bou.amine.apps.readerforselfossv2.android.R import bou.amine.apps.readerforselfossv2.android.R
import bou.amine.apps.readerforselfossv2.android.api.mercury.MercuryApi import bou.amine.apps.readerforselfossv2.android.api.mercury.MercuryApi
import bou.amine.apps.readerforselfossv2.android.api.mercury.ParsedContent import bou.amine.apps.readerforselfossv2.android.api.mercury.ParsedContent
import bou.amine.apps.readerforselfossv2.android.databinding.FragmentArticleBinding import bou.amine.apps.readerforselfossv2.android.databinding.FragmentArticleBinding
import bou.amine.apps.readerforselfossv2.android.model.* import bou.amine.apps.readerforselfossv2.android.model.*
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_1_2
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_2_3
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_3_4
import bou.amine.apps.readerforselfossv2.android.themes.AppColors import bou.amine.apps.readerforselfossv2.android.themes.AppColors
import bou.amine.apps.readerforselfossv2.android.utils.* import bou.amine.apps.readerforselfossv2.android.utils.*
import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper
@ -71,7 +66,6 @@ class ArticleFragment : Fragment(), DIAware {
private lateinit var allImages : ArrayList<String> private lateinit var allImages : ArrayList<String>
private lateinit var fab: FloatingActionButton private lateinit var fab: FloatingActionButton
private lateinit var appColors: AppColors private lateinit var appColors: AppColors
private lateinit var db: AppDatabase
private lateinit var textAlignment: String private lateinit var textAlignment: String
private lateinit var config: Config private lateinit var config: Config
private var _binding: FragmentArticleBinding? = null private var _binding: FragmentArticleBinding? = null
@ -103,11 +97,6 @@ class ArticleFragment : Fragment(), DIAware {
val pi: ParecelableItem = requireArguments().getParcelable(ARG_ITEMS)!! val pi: ParecelableItem = requireArguments().getParcelable(ARG_ITEMS)!!
item = pi.toModel() item = pi.toModel()
db = Room.databaseBuilder(
requireContext(),
AppDatabase::class.java, "selfoss-database"
).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).addMigrations(MIGRATION_3_4).build()
} }
override fun onCreateView( override fun onCreateView(

View File

@ -1,28 +0,0 @@
package bou.amine.apps.readerforselfossv2.android.persistence
import android.content.Context
import androidx.room.Room
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
import bou.amine.apps.readerforselfossv2.android.persistence.entities.AndroidItemEntity
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_1_2
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_2_3
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_3_4
import bou.amine.apps.readerforselfossv2.dao.DeviceDatabase
class AndroidDeviceDatabase(applicationContext: Context): DeviceDatabase<AndroidItemEntity> {
var db: AppDatabase = Room.databaseBuilder(
applicationContext,
AppDatabase::class.java, "selfoss-database"
).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).addMigrations(MIGRATION_3_4).build()
override suspend fun items(): List<AndroidItemEntity> = db.itemsDao().items()
override suspend fun insertAllItems(vararg items: AndroidItemEntity) = db.itemsDao().insertAllItems(*items)
override suspend fun deleteAllItems() = db.itemsDao().deleteAllItems()
override suspend fun delete(item: AndroidItemEntity) = db.itemsDao().delete(item)
override suspend fun updateItem(item: AndroidItemEntity) = db.itemsDao().updateItem(item)
}

View File

@ -1,40 +0,0 @@
package bou.amine.apps.readerforselfossv2.android.persistence
import bou.amine.apps.readerforselfossv2.android.persistence.entities.AndroidItemEntity
import bou.amine.apps.readerforselfossv2.android.utils.persistence.toEntity
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
import bou.amine.apps.readerforselfossv2.service.DeviceDataBaseService
import bou.amine.apps.readerforselfossv2.service.SearchService
class AndroidDeviceDatabaseService(db: AndroidDeviceDatabase, searchService: SearchService) :
DeviceDataBaseService<AndroidItemEntity>(db, searchService) {
override suspend fun updateDatabase() {
if (itemsCaching) {
if (items.isEmpty()) {
getFromDB()
}
db.deleteAllItems()
db.insertAllItems(*(items.map { it.toEntity() }).toTypedArray())
}
}
override suspend fun clearDBItems() {
db.deleteAllItems()
}
override fun appendNewItems(newItems: List<SelfossModel.Item>) {
var oldItems = items
if (oldItems != newItems) {
oldItems = oldItems.filter { item -> newItems.find { it.id == item.id } == null } as ArrayList<SelfossModel.Item>
oldItems.addAll(newItems)
items = oldItems
sortItems()
getFocusedItems()
}
}
override fun getFromDB() {
TODO("Not yet implemented")
}
}

View File

@ -1,23 +0,0 @@
package bou.amine.apps.readerforselfossv2.android.persistence.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import bou.amine.apps.readerforselfossv2.android.persistence.entities.ActionEntity
@Dao
interface ActionsDao {
@Query("SELECT * FROM actions order by id asc")
suspend 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)
}

View File

@ -1,36 +0,0 @@
package bou.amine.apps.readerforselfossv2.android.persistence.dao
import androidx.room.Delete
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import bou.amine.apps.readerforselfossv2.android.persistence.entities.SourceEntity
import bou.amine.apps.readerforselfossv2.android.persistence.entities.TagEntity
@Dao
interface DrawerDataDao {
@Query("SELECT * FROM tags")
fun tags(): List<TagEntity>
@Query("SELECT * FROM sources")
fun sources(): List<SourceEntity>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAllTags(vararg tags: TagEntity)
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAllSources(vararg sources: SourceEntity)
@Query("DELETE FROM tags")
fun deleteAllTags()
@Query("DELETE FROM sources")
fun deleteAllSources()
@Delete
fun deleteTag(tag: TagEntity)
@Delete
fun deleteSource(source: SourceEntity)
}

View File

@ -1,29 +0,0 @@
package bou.amine.apps.readerforselfossv2.android.persistence.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import bou.amine.apps.readerforselfossv2.android.persistence.entities.AndroidItemEntity
import androidx.room.Update
@Dao
interface ItemsDao {
@Query("SELECT * FROM items order by id desc")
suspend fun items(): List<AndroidItemEntity>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAllItems(vararg items: AndroidItemEntity)
@Query("DELETE FROM items")
suspend fun deleteAllItems()
@Delete
suspend fun delete(item: AndroidItemEntity)
@Update
suspend fun updateItem(item: AndroidItemEntity)
}

View File

@ -1,20 +0,0 @@
package bou.amine.apps.readerforselfossv2.android.persistence.database
import androidx.room.RoomDatabase
import androidx.room.Database
import bou.amine.apps.readerforselfossv2.android.persistence.dao.ActionsDao
import bou.amine.apps.readerforselfossv2.android.persistence.dao.DrawerDataDao
import bou.amine.apps.readerforselfossv2.android.persistence.dao.ItemsDao
import bou.amine.apps.readerforselfossv2.android.persistence.entities.ActionEntity
import bou.amine.apps.readerforselfossv2.android.persistence.entities.AndroidItemEntity
import bou.amine.apps.readerforselfossv2.android.persistence.entities.SourceEntity
import bou.amine.apps.readerforselfossv2.android.persistence.entities.TagEntity
@Database(entities = [TagEntity::class, SourceEntity::class, AndroidItemEntity::class, ActionEntity::class], version = 4)
abstract class AppDatabase : RoomDatabase() {
abstract fun drawerDataDao(): DrawerDataDao
abstract fun itemsDao(): ItemsDao
abstract fun actionsDao(): ActionsDao
}

View File

@ -1,22 +0,0 @@
package bou.amine.apps.readerforselfossv2.android.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
}

View File

@ -1,33 +0,0 @@
package bou.amine.apps.readerforselfossv2.android.persistence.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
@Entity(tableName = "items")
data class AndroidItemEntity(
@PrimaryKey
@ColumnInfo(name = "id")
val id: String,
@ColumnInfo(name = "datetime")
val datetime: String,
@ColumnInfo(name = "title")
val title: String,
@ColumnInfo(name = "content")
val content: String,
@ColumnInfo(name = "unread")
val unread: Boolean,
@ColumnInfo(name = "starred")
var starred: Boolean,
@ColumnInfo(name = "thumbnail")
val thumbnail: String?,
@ColumnInfo(name = "icon")
val icon: String?,
@ColumnInfo(name = "link")
val link: String,
@ColumnInfo(name = "sourcetitle")
val sourcetitle: String,
@ColumnInfo(name = "tags")
val tags: String
)

View File

@ -1,33 +0,0 @@
package bou.amine.apps.readerforselfossv2.android.persistence.entities
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "tags")
data class TagEntity(
@PrimaryKey
@ColumnInfo(name = "tag")
val tag: String,
@ColumnInfo(name = "color")
val color: String,
@ColumnInfo(name = "unread")
val unread: Int
)
@Entity(tableName = "sources")
data class SourceEntity(
@PrimaryKey
@ColumnInfo(name = "id")
val id: String,
@ColumnInfo(name = "title")
val title: String,
@ColumnInfo(name = "tags")
val tags: String,
@ColumnInfo(name = "spout")
val spout: String,
@ColumnInfo(name = "error")
val error: String,
@ColumnInfo(name = "icon")
val icon: String
)

View File

@ -1,34 +0,0 @@
package bou.amine.apps.readerforselfossv2.android.persistence.migrations
import androidx.sqlite.db.SupportSQLiteDatabase
import androidx.room.migration.Migration
val MIGRATION_1_2: Migration = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
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`))")
}
}
val MIGRATION_3_4: Migration = object : Migration(3, 4) {
override fun migrate(database: SupportSQLiteDatabase) {
// @see https://stackoverflow.com/questions/57392015/how-to-migrate-not-null-table-column-into-null-in-android-room-database
// Create the new table
database.execSQL("CREATE TABLE IF NOT EXISTS `itemstmp` (`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, `icon` TEXT, `link` TEXT NOT NULL, `sourcetitle` TEXT NOT NULL, `tags` TEXT NOT NULL, PRIMARY KEY(`id`))")
// Copy the data
database.execSQL(
"INSERT INTO itemstmp (`id`, `datetime`, `title`, `content`, `unread`, `starred`, `thumbnail`, `icon`, `link`, `sourcetitle`, `tags`) SELECT `id`, `datetime`, `title`, `content`, `unread`, `starred`, `thumbnail`, `icon`, `link`, `sourcetitle`, `tags` FROM items")
// Remove the old table
database.execSQL("DROP TABLE items")
// Change the table name to the correct one
database.execSQL("ALTER TABLE itemstmp RENAME TO items")
}
}

View File

@ -1,72 +0,0 @@
package bou.amine.apps.readerforselfossv2.android.utils.persistence
import bou.amine.apps.readerforselfossv2.android.model.getSourceTitle
import bou.amine.apps.readerforselfossv2.android.model.getTitleDecoded
import bou.amine.apps.readerforselfossv2.android.persistence.entities.AndroidItemEntity
import bou.amine.apps.readerforselfossv2.android.persistence.entities.SourceEntity
import bou.amine.apps.readerforselfossv2.android.persistence.entities.TagEntity
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
fun TagEntity.toView(): SelfossModel.Tag =
SelfossModel.Tag(
this.tag,
this.color,
this.unread
)
fun SourceEntity.toView(): SelfossModel.Source =
SelfossModel.Source(
this.id.toInt(),
this.title,
this.tags.split(","),
this.spout,
this.error,
this.icon
)
fun SelfossModel.Source.toEntity(): SourceEntity =
SourceEntity(
this.id.toString(),
this.getTitleDecoded(),
this.tags.joinToString(","),
this.spout,
this.error,
this.icon.orEmpty()
)
fun SelfossModel.Tag.toEntity(): TagEntity =
TagEntity(
this.tag,
this.color,
this.unread
)
fun AndroidItemEntity.toView(): SelfossModel.Item =
SelfossModel.Item(
this.id.toInt(),
this.datetime,
this.title,
this.content,
this.unread,
this.starred,
this.thumbnail,
this.icon,
this.link,
this.sourcetitle,
this.tags.split(",")
)
fun SelfossModel.Item.toEntity(): AndroidItemEntity =
AndroidItemEntity(
this.id.toString(),
this.datetime,
this.getTitleDecoded(),
this.content,
this.unread,
this.starred,
this.thumbnail,
this.icon,
this.link,
this.getSourceTitle(),
this.tags.joinToString(",")
)

View File

@ -6,10 +6,13 @@ buildscript {
} }
dependencies { dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10")
classpath("com.android.tools.build:gradle:7.2.1") classpath("com.android.tools.build:gradle:7.2.2")
// sonarquve // sonarquve
classpath("org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.4.0.2513") classpath("org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.4.0.2513")
// SqlDelight
classpath("com.squareup.sqldelight:gradle-plugin:1.5.3")
} }
} }

View File

@ -1,6 +1,14 @@
object SqlDelight {
const val runtime = "com.squareup.sqldelight:runtime:1.5.3"
const val android = "com.squareup.sqldelight:android-driver:1.5.3"
const val native = "com.squareup.sqldelight:native-driver:1.5.3"
}
plugins { plugins {
kotlin("multiplatform") kotlin("multiplatform")
id("com.android.library") id("com.android.library")
id("com.squareup.sqldelight")
kotlin("plugin.serialization") version "1.4.10" kotlin("plugin.serialization") version "1.4.10"
} }
@ -39,6 +47,9 @@ kotlin {
// Network information // Network information
implementation("com.github.ln-12:multiplatform-connectivity-status:1.3.0") implementation("com.github.ln-12:multiplatform-connectivity-status:1.3.0")
// Sql
implementation(SqlDelight.runtime)
} }
} }
val commonTest by getting { val commonTest by getting {
@ -50,6 +61,9 @@ kotlin {
val androidMain by getting { val androidMain by getting {
dependencies { dependencies {
implementation("io.ktor:ktor-client-android:2.0.1") implementation("io.ktor:ktor-client-android:2.0.1")
// Sql
implementation(SqlDelight.android)
} }
} }
val androidTest by getting { val androidTest by getting {
@ -66,6 +80,11 @@ kotlin {
iosX64Main.dependsOn(this) iosX64Main.dependsOn(this)
iosArm64Main.dependsOn(this) iosArm64Main.dependsOn(this)
//iosSimulatorArm64Main.dependsOn(this) //iosSimulatorArm64Main.dependsOn(this)
// Sql
dependencies {
implementation(SqlDelight.native)
}
} }
val iosX64Test by getting val iosX64Test by getting
val iosArm64Test by getting val iosArm64Test by getting
@ -93,4 +112,12 @@ android {
sourceCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8
} }
} }
sqldelight {
database("ReaderForSelfossDB") {
packageName = "bou.amine.apps.readerforselfossv2.dao"
sourceFolders = listOf("sqldelight")
}
}

View File

@ -0,0 +1,10 @@
package bou.amine.apps.readerforselfossv2.dao
import android.content.Context
import com.squareup.sqldelight.android.AndroidSqliteDriver
import com.squareup.sqldelight.db.SqlDriver
actual class DriverFactory(private val context: Context) {
actual fun createDriver(): SqlDriver {
return AndroidSqliteDriver(ReaderForSelfossDB.Schema, context, "ReaderForSelfossV2-android.db")
}
}

View File

@ -0,0 +1,16 @@
package bou.amine.apps.readerforselfossv2.utils
import android.text.Html
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
actual fun SelfossModel.Item.getTitleDecoded(): String {
return Html.fromHtml(title).toString()
}
actual fun SelfossModel.Item.getSourceTitle(): String {
return Html.fromHtml(sourcetitle).toString()
}
actual fun SelfossModel.Source.getTitleDecoded(): String {
return Html.fromHtml(title).toString()
}

View File

@ -1,9 +1,7 @@
package bou.amine.apps.readerforselfossv2.dao package bou.amine.apps.readerforselfossv2.dao
interface DeviceDatabase<ItemEntity> { import com.squareup.sqldelight.db.SqlDriver
suspend fun items(): List<ItemEntity>
suspend fun insertAllItems(vararg items: ItemEntity) expect class DriverFactory {
suspend fun deleteAllItems() fun createDriver(): SqlDriver
suspend fun delete(item: ItemEntity) }
suspend fun updateItem(item: ItemEntity)
}

View File

@ -1,10 +1,15 @@
package bou.amine.apps.readerforselfossv2.repository package bou.amine.apps.readerforselfossv2.repository
import bou.amine.apps.readerforselfossv2.dao.ACTION
import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB
import bou.amine.apps.readerforselfossv2.dao.SOURCE
import bou.amine.apps.readerforselfossv2.dao.TAG
import bou.amine.apps.readerforselfossv2.rest.SelfossApi import bou.amine.apps.readerforselfossv2.rest.SelfossApi
import bou.amine.apps.readerforselfossv2.rest.SelfossModel import bou.amine.apps.readerforselfossv2.rest.SelfossModel
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
import bou.amine.apps.readerforselfossv2.utils.DateUtils import bou.amine.apps.readerforselfossv2.utils.DateUtils
import bou.amine.apps.readerforselfossv2.utils.ItemType import bou.amine.apps.readerforselfossv2.utils.ItemType
import bou.amine.apps.readerforselfossv2.utils.toEntity
import com.github.ln_12.library.ConnectivityStatus import com.github.ln_12.library.ConnectivityStatus
import com.russhwolf.settings.Settings import com.russhwolf.settings.Settings
import io.github.aakira.napier.Napier import io.github.aakira.napier.Napier
@ -12,7 +17,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class Repository(private val api: SelfossApi, private val apiDetails: ApiDetailsService, val connectivityStatus: ConnectivityStatus) { class Repository(private val api: SelfossApi, private val apiDetails: ApiDetailsService, val connectivityStatus: ConnectivityStatus, private val db: ReaderForSelfossDB) {
val settings = Settings() val settings = Settings()
var items = ArrayList<SelfossModel.Item>() var items = ArrayList<SelfossModel.Item>()
@ -342,5 +347,33 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
fun isNetworkAvailable() = isConnectionAvailable.value && !offlineOverride fun isNetworkAvailable() = isConnectionAvailable.value && !offlineOverride
// TODO: Handle offline actions fun getDBActions(): List<ACTION> =
db.actionsQueries.actions().executeAsList()
fun deleteDBAction(action: ACTION) =
db.actionsQueries.deleteAction(action.id)
fun getDBTags(): List<TAG> = db.tagsQueries.tags().executeAsList()
fun getDBSources(): List<SOURCE> = db.sourcesQueries.sources().executeAsList()
fun resetDBTagsWithData(tagEntities: List<SelfossModel.Tag>) {
db.tagsQueries.deleteAllTags()
db.tagsQueries.transaction {
tagEntities.forEach { tag ->
db.tagsQueries.insertTag(tag.toEntity())
}
}
}
fun resetDBSourcesWithData(sources: List<SelfossModel.Source>) {
db.sourcesQueries.deleteAllSources()
db.sourcesQueries.transaction {
sources.forEach { source ->
db.sourcesQueries.insertSource(source.toEntity())
}
}
}
} }

View File

@ -1,34 +0,0 @@
package bou.amine.apps.readerforselfossv2.service
import bou.amine.apps.readerforselfossv2.dao.DeviceDatabase
import bou.amine.apps.readerforselfossv2.utils.parseDate
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
abstract class DeviceDataBaseService<ItemEntity>(val db: DeviceDatabase<ItemEntity>, private val searchService: SearchService) {
var itemsCaching = false
var items: ArrayList<SelfossModel.Item> = arrayListOf()
get() {
return ArrayList(field)
}
set(value) {
field = ArrayList(value)
}
abstract suspend fun updateDatabase()
abstract suspend fun clearDBItems()
abstract fun appendNewItems(items: List<SelfossModel.Item>)
abstract fun getFromDB()
fun sortItems() {
val tmpItems = ArrayList(items.sortedByDescending { it.parseDate(searchService.dateUtils) })
items = tmpItems
}
// This filtered items from items val. Do not use
fun getFocusedItems() {}
fun computeBadges() {
searchService.badgeUnread = items.filter { item -> item.unread }.size
searchService.badgeStarred = items.filter { item -> item.starred }.size
searchService.badgeAll = items.size
}
}

View File

@ -0,0 +1,74 @@
package bou.amine.apps.readerforselfossv2.utils
import bou.amine.apps.readerforselfossv2.dao.ITEM
import bou.amine.apps.readerforselfossv2.dao.SOURCE
import bou.amine.apps.readerforselfossv2.dao.TAG
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
expect fun SelfossModel.Source.getTitleDecoded(): String
expect fun SelfossModel.Item.getTitleDecoded(): String
expect fun SelfossModel.Item.getSourceTitle(): String
fun TAG.toView(): SelfossModel.Tag =
SelfossModel.Tag(
this.name,
this.color,
this.unread.toInt()
)
fun SOURCE.toView(): SelfossModel.Source =
SelfossModel.Source(
this.id.toInt(),
this.title,
this.tags.split(","),
this.spout,
this.error,
this.icon
)
fun SelfossModel.Source.toEntity(): SOURCE =
SOURCE(
this.id.toString(),
this.getTitleDecoded(),
this.tags.joinToString(","),
this.spout,
this.error,
this.icon.orEmpty()
)
fun SelfossModel.Tag.toEntity(): TAG =
TAG(
this.tag,
this.color,
this.unread.toLong()
)
fun ITEM.toView(): SelfossModel.Item =
SelfossModel.Item(
this.id.toInt(),
this.datetime,
this.title,
this.content,
this.unread,
this.starred,
this.thumbnail,
this.icon,
this.link,
this.sourcetitle,
this.tags.split(",")
)
fun SelfossModel.Item.toEntity(): ITEM =
ITEM(
this.id.toString(),
this.datetime,
this.getTitleDecoded(),
this.content,
this.unread,
this.starred,
this.thumbnail,
this.icon,
this.link,
this.getSourceTitle(),
this.tags.joinToString(",")
)

View File

@ -0,0 +1,18 @@
CREATE TABLE `ACTION` (
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
`articleid` TEXT NOT NULL,
`read` INTEGER AS Boolean DEFAULT 0 NOT NULL,
`unread` INTEGER AS Boolean DEFAULT 0 NOT NULL,
`starred` INTEGER AS Boolean DEFAULT 0 NOT NULL,
`unstarred` INTEGER AS Boolean DEFAULT 0 NOT NULL
);
actions:
SELECT *
FROM `ACTION`;
insertAction:
INSERT OR REPLACE INTO `ACTION` (`articleid`, `read`, `unread`, `starred`, `unstarred`) VALUES (?, ?, ?, ?, ?);
deleteAction:
DELETE FROM `ACTION` WHERE id = ?;

View File

@ -0,0 +1,30 @@
CREATE TABLE ITEM (
`id` TEXT NOT NULL,
`datetime` TEXT NOT NULL,
`title` TEXT NOT NULL,
`content` TEXT NOT NULL,
`unread` INTEGER AS Boolean DEFAULT 0 NOT NULL,
`starred` INTEGER AS Boolean DEFAULT 0 NOT NULL,
`thumbnail` TEXT,
`icon` TEXT,
`link` TEXT NOT NULL,
`sourcetitle` TEXT NOT NULL,
`tags` TEXT NOT NULL,
PRIMARY KEY(`id`)
);
CREATE INDEX item_title ON ITEM(`title`);
CREATE INDEX item_source ON ITEM(`sourcetitle`);
items:
SELECT * FROM ITEM ORDER BY `id` DESC;
insertItem:
INSERT OR REPLACE INTO ITEM VALUES ?;
deleteItem:
DELETE FROM ITEM WHERE `id` = ?;
updateItem:
UPDATE ITEM SET `datetime` = ?, `title` = ?, `content` = ?, `unread` = ?, `starred` = ?, `thumbnail` = ?, `icon` = ?, `link` = ?, `sourcetitle` = ?, `tags` = ? WHERE `id` = ?;

View File

@ -0,0 +1,21 @@
CREATE TABLE SOURCE (
`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`)
);
sources:
SELECT * FROM SOURCE;
insertSource:
INSERT OR REPLACE INTO SOURCE VALUES ?;
deleteAllSources:
DELETE FROM SOURCE;
deleteSource:
DELETE FROM SOURCE WHERE `id` = ?;

View File

@ -0,0 +1,20 @@
CREATE TABLE TAG (
`name` TEXT NOT NULL,
`color` TEXT NOT NULL,
`unread` INTEGER NOT NULL,
PRIMARY KEY(`name`)
);
CREATE INDEX tag_name ON TAG(`name`);
tags:
SELECT * FROM TAG;
insertTag:
INSERT OR REPLACE INTO TAG VALUES ?;
deleteAllTags:
DELETE FROM TAG;
deleteTag:
DELETE FROM TAG WHERE `name` = ? AND `color` = ?;

View File

@ -0,0 +1,10 @@
package bou.amine.apps.readerforselfossv2.dao
import com.squareup.sqldelight.db.SqlDriver
import com.squareup.sqldelight.drivers.native.NativeSqliteDriver
actual class DriverFactory {
actual fun createDriver(): SqlDriver {
return NativeSqliteDriver(ReaderForSelfossDB.Schema, "ReaderForSelfossV2-IOS.db")
}
}

View File

@ -0,0 +1,12 @@
package bou.amine.apps.readerforselfossv2.utils
actual class DateUtils actual constructor(apiMajorVersion: Int) {
actual fun parseDate(dateString: String): Long {
TODO("Not yet implemented")
}
actual fun parseRelativeDate(dateString: String): String {
TODO("Not yet implemented")
}
}

View File

@ -0,0 +1,15 @@
package bou.amine.apps.readerforselfossv2.utils
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
actual fun SelfossModel.Item.getTitleDecoded(): String {
TODO("Not yet implemented")
}
actual fun SelfossModel.Item.getSourceTitle(): String {
TODO("Not yet implemented")
}
actual fun SelfossModel.Source.getTitleDecoded(): String {
TODO("Not yet implemented")
}

View File

@ -0,0 +1,10 @@
package bou.amine.apps.readerforselfossv2.dao
import com.squareup.sqldelight.db.SqlDriver
import com.squareup.sqldelight.drivers.native.NativeSqliteDriver
actual class DriverFactory {
actual fun createDriver(): SqlDriver {
return NativeSqliteDriver(ReaderForSelfossDB.Schema, "ReaderForSelfossV2-IOS.db")
}
}

View File

@ -0,0 +1,12 @@
package bou.amine.apps.readerforselfossv2.utils
actual class DateUtils actual constructor(apiMajorVersion: Int) {
actual fun parseDate(dateString: String): Long {
TODO("Not yet implemented")
}
actual fun parseRelativeDate(dateString: String): String {
TODO("Not yet implemented")
}
}

View File

@ -0,0 +1,15 @@
package bou.amine.apps.readerforselfossv2.utils
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
actual fun SelfossModel.Item.getTitleDecoded(): String {
TODO("Not yet implemented")
}
actual fun SelfossModel.Item.getSourceTitle(): String {
TODO("Not yet implemented")
}
actual fun SelfossModel.Source.getTitleDecoded(): String {
TODO("Not yet implemented")
}