diff --git a/api/src/main/java/com/readrops/api/services/Credentials.kt b/api/src/main/java/com/readrops/api/services/Credentials.kt index 92375c13..4d479d01 100644 --- a/api/src/main/java/com/readrops/api/services/Credentials.kt +++ b/api/src/main/java/com/readrops/api/services/Credentials.kt @@ -12,9 +12,9 @@ abstract class Credentials(val authorization: String?, val url: String) { companion object { fun toCredentials(account: Account): Credentials { - val endPoint = getEndPoint(account.accountType!!) + val endPoint = getEndPoint(account.type!!) - return when (account.accountType) { + return when (account.type) { AccountType.NEXTCLOUD_NEWS -> NextcloudNewsCredentials(account.login, account.password, account.url + endPoint) AccountType.FRESHRSS -> FreshRSSCredentials(account.token, account.url + endPoint) AccountType.FEVER -> FeverCredentials(account.login, account.password, account.url + endPoint) diff --git a/app/src/androidTest/java/com/readrops/app/GetFoldersWithFeedsTest.kt b/app/src/androidTest/java/com/readrops/app/GetFoldersWithFeedsTest.kt index 40801a10..cfdfe499 100644 --- a/app/src/androidTest/java/com/readrops/app/GetFoldersWithFeedsTest.kt +++ b/app/src/androidTest/java/com/readrops/app/GetFoldersWithFeedsTest.kt @@ -22,7 +22,7 @@ class GetFoldersWithFeedsTest { private lateinit var database: Database private lateinit var getFoldersWithFeeds: GetFoldersWithFeeds - private val account = Account(accountType = AccountType.LOCAL) + private val account = Account(type = AccountType.LOCAL) @Before fun before() { diff --git a/app/src/androidTest/java/com/readrops/app/LocalRSSRepositoryTest.kt b/app/src/androidTest/java/com/readrops/app/LocalRSSRepositoryTest.kt index 8b87f364..40d0da32 100644 --- a/app/src/androidTest/java/com/readrops/app/LocalRSSRepositoryTest.kt +++ b/app/src/androidTest/java/com/readrops/app/LocalRSSRepositoryTest.kt @@ -32,7 +32,7 @@ import kotlin.test.assertTrue class LocalRSSRepositoryTest : KoinTest { private val mockServer: MockWebServer = MockWebServer() - private val account = Account(accountType = AccountType.LOCAL) + private val account = Account(type = AccountType.LOCAL) private lateinit var database: Database private lateinit var repository: LocalRSSRepository private lateinit var feeds: List diff --git a/app/src/androidTest/java/com/readrops/app/SyncResultAnalyserTest.kt b/app/src/androidTest/java/com/readrops/app/SyncResultAnalyserTest.kt index 983b428a..eedda6c1 100644 --- a/app/src/androidTest/java/com/readrops/app/SyncResultAnalyserTest.kt +++ b/app/src/androidTest/java/com/readrops/app/SyncResultAnalyserTest.kt @@ -32,20 +32,20 @@ class SyncAnalyzerTest { NullPointerException("Notification content shouldn't be null") private val account1 = Account( - accountName = "test account 1", - accountType = AccountType.FRESHRSS, + name = "test account 1", + type = AccountType.FRESHRSS, isNotificationsEnabled = true ) private val account2 = Account( - accountName = "test account 2", - accountType = AccountType.NEXTCLOUD_NEWS, + name = "test account 2", + type = AccountType.NEXTCLOUD_NEWS, isNotificationsEnabled = false ) private val account3 = Account( - accountName = "test account 3", - accountType = AccountType.LOCAL, + name = "test account 3", + type = AccountType.LOCAL, isNotificationsEnabled = true ) @@ -124,7 +124,7 @@ class SyncAnalyzerTest { syncAnalyzer.getNotificationContent(mapOf(account1 to syncResult))?.let { content -> assertEquals(context.getString(R.string.new_items, 2), content.text) - assertEquals(account1.accountName, content.title) + assertEquals(account1.name, content.title) assertTrue(content.largeIcon != null) assertTrue(content.accountId > 0) } ?: throw nullContentException diff --git a/app/src/main/java/com/readrops/app/AppModule.kt b/app/src/main/java/com/readrops/app/AppModule.kt index 1cd2a362..b44ee70f 100644 --- a/app/src/main/java/com/readrops/app/AppModule.kt +++ b/app/src/main/java/com/readrops/app/AppModule.kt @@ -59,7 +59,7 @@ val appModule = module { single { GetFoldersWithFeeds(get()) } factory { (account: Account) -> - when (account.accountType) { + when (account.type) { AccountType.LOCAL -> LocalRSSRepository(get(), get(), account) AccountType.FRESHRSS -> FreshRSSRepository( database = get(), diff --git a/app/src/main/java/com/readrops/app/account/AccountScreenModel.kt b/app/src/main/java/com/readrops/app/account/AccountScreenModel.kt index 1e998f8b..e86dca6e 100644 --- a/app/src/main/java/com/readrops/app/account/AccountScreenModel.kt +++ b/app/src/main/java/com/readrops/app/account/AccountScreenModel.kt @@ -178,8 +178,8 @@ class AccountScreenModel( fun createLocalAccount() { val context = get() val account = Account( - accountName = context.getString(AccountType.LOCAL.typeName), - accountType = AccountType.LOCAL, + name = context.getString(AccountType.LOCAL.nameRes), + type = AccountType.LOCAL, isCurrentAccount = true ) @@ -212,7 +212,7 @@ class AccountScreenModel( @Stable data class AccountState( - val account: Account = Account(accountName = "account", accountType = AccountType.LOCAL), + val account: Account = Account(name = "account", type = AccountType.LOCAL), val dialog: DialogState? = null, val synchronizationErrors: ErrorResult? = null, val error: Exception? = null, diff --git a/app/src/main/java/com/readrops/app/account/AccountTab.kt b/app/src/main/java/com/readrops/app/account/AccountTab.kt index 482e2ac5..1b084669 100644 --- a/app/src/main/java/com/readrops/app/account/AccountTab.kt +++ b/app/src/main/java/com/readrops/app/account/AccountTab.kt @@ -192,7 +192,7 @@ object AccountTab : Tab { modifier = Modifier.weight(1f) ) { Image( - painter = adaptiveIconPainterResource(id = state.account.accountType!!.iconRes), + painter = adaptiveIconPainterResource(id = state.account.type!!.iconRes), contentDescription = null, modifier = Modifier.size(48.dp) ) @@ -201,7 +201,7 @@ object AccountTab : Tab { Column { Text( - text = state.account.accountName!!, + text = state.account.name!!, style = MaterialTheme.typography.titleLarge, maxLines = 1, overflow = TextOverflow.Ellipsis @@ -224,7 +224,7 @@ object AccountTab : Tab { ThreeDotsMenu( items = mapOf(1 to stringResource(id = R.string.rename_account)), onItemClick = { - screenModel.openDialog(DialogState.RenameAccount(state.account.accountName!!)) + screenModel.openDialog(DialogState.RenameAccount(state.account.name!!)) }, ) } @@ -303,8 +303,8 @@ object AccountTab : Tab { for (account in state.accounts) { SelectableImageText( - image = adaptiveIconPainterResource(id = account.accountType!!.iconRes), - text = account.accountName!!, + image = adaptiveIconPainterResource(id = account.type!!.iconRes), + text = account.name!!, style = MaterialTheme.typography.titleMedium, padding = MaterialTheme.spacing.mediumSpacing, spacing = MaterialTheme.spacing.mediumSpacing, @@ -358,8 +358,8 @@ object AccountTab : Tab { screenModel.createLocalAccount() } else { val account = Account( - accountType = accountType, - accountName = context.resources.getString(accountType.typeName) + type = accountType, + name = context.resources.getString(accountType.nameRes) ) navigator.push( AccountCredentialsScreen( diff --git a/app/src/main/java/com/readrops/app/account/credentials/AccountCredentialsScreen.kt b/app/src/main/java/com/readrops/app/account/credentials/AccountCredentialsScreen.kt index a7ebef3a..e8ce98a8 100644 --- a/app/src/main/java/com/readrops/app/account/credentials/AccountCredentialsScreen.kt +++ b/app/src/main/java/com/readrops/app/account/credentials/AccountCredentialsScreen.kt @@ -117,7 +117,7 @@ class AccountCredentialsScreen( .verticalScroll(rememberScrollState()) ) { Image( - painter = adaptiveIconPainterResource(id = account.accountType!!.iconRes), + painter = adaptiveIconPainterResource(id = account.type!!.iconRes), contentDescription = null, modifier = Modifier.size(48.dp) ) @@ -125,7 +125,7 @@ class AccountCredentialsScreen( ShortSpacer() Text( - text = stringResource(id = account.accountType!!.typeName), + text = stringResource(id = account.type!!.nameRes), style = MaterialTheme.typography.headlineMedium ) @@ -155,7 +155,7 @@ class AccountCredentialsScreen( state.urlError != null -> { Text(text = state.urlError!!.errorText()) } - account.accountType == AccountType.FEVER -> { + account.type == AccountType.FEVER -> { Text(text = stringResource(R.string.provide_full_url)) } else -> { @@ -211,7 +211,7 @@ class AccountCredentialsScreen( state.passwordError != null -> { Text(text = state.passwordError!!.errorText()) } - account.accountType == AccountType.FRESHRSS -> { + account.type == AccountType.FRESHRSS -> { Text(text = stringResource(id = R.string.password_helper)) } } diff --git a/app/src/main/java/com/readrops/app/account/credentials/AccountCredentialsScreenModel.kt b/app/src/main/java/com/readrops/app/account/credentials/AccountCredentialsScreenModel.kt index ee7200f4..ae144bd9 100644 --- a/app/src/main/java/com/readrops/app/account/credentials/AccountCredentialsScreenModel.kt +++ b/app/src/main/java/com/readrops/app/account/credentials/AccountCredentialsScreenModel.kt @@ -28,14 +28,14 @@ class AccountCredentialsScreenModel( if (mode == AccountCredentialsScreenMode.EDIT_CREDENTIALS) { mutableState.update { it.copy( - name = account.accountName!!, + name = account.name!!, url = account.url!!, login = account.login!!, password = account.password!! ) } } else { - mutableState.update { it.copy(name = account.accountName!!) } + mutableState.update { it.copy(name = account.name!!) } } } @@ -61,10 +61,10 @@ class AccountCredentialsScreenModel( val newAccount = account.copy( url = normalizedUrl, - accountName = name, + name = name, login = login, password = password, - accountType = account.accountType, + type = account.type, isCurrentAccount = true ) diff --git a/app/src/main/java/com/readrops/app/account/selection/AccountSelectionDialog.kt b/app/src/main/java/com/readrops/app/account/selection/AccountSelectionDialog.kt index 3bf040e4..88e35bfd 100644 --- a/app/src/main/java/com/readrops/app/account/selection/AccountSelectionDialog.kt +++ b/app/src/main/java/com/readrops/app/account/selection/AccountSelectionDialog.kt @@ -22,11 +22,10 @@ fun AccountSelectionDialog( onDismiss = onDismiss ) { AccountType.entries - .filter { it != AccountType.FEEDLY } .forEach { type -> SelectableImageText( image = adaptiveIconPainterResource(id = type.iconRes), - text = stringResource(id = type.typeName), + text = stringResource(id = type.nameRes), style = MaterialTheme.typography.titleMedium, spacing = MaterialTheme.spacing.mediumSpacing, padding = MaterialTheme.spacing.shortSpacing, diff --git a/app/src/main/java/com/readrops/app/account/selection/AccountSelectionScreen.kt b/app/src/main/java/com/readrops/app/account/selection/AccountSelectionScreen.kt index a5342a80..8c6f8727 100644 --- a/app/src/main/java/com/readrops/app/account/selection/AccountSelectionScreen.kt +++ b/app/src/main/java/com/readrops/app/account/selection/AccountSelectionScreen.kt @@ -95,8 +95,8 @@ class AccountSelectionScreen : AndroidScreen() { val accountType = (state.navState as NavState.GoToAccountCredentialsScreen).accountType val account = Account( - accountType = accountType, - accountName = stringResource(id = accountType.typeName) + type = accountType, + name = stringResource(id = accountType.nameRes) ) navigator.push( @@ -159,7 +159,7 @@ class AccountSelectionScreen : AndroidScreen() { SelectableImageText( image = adaptiveIconPainterResource(id = R.mipmap.ic_launcher), - text = stringResource(id = AccountType.LOCAL.typeName), + text = stringResource(id = AccountType.LOCAL.nameRes), style = MaterialTheme.typography.bodyLarge, spacing = MaterialTheme.spacing.mediumSpacing, padding = MaterialTheme.spacing.mediumSpacing, @@ -187,11 +187,10 @@ class AccountSelectionScreen : AndroidScreen() { AccountType.entries .filter { it != AccountType.LOCAL } - .filter { it != AccountType.FEEDLY } .forEach { accountType -> SelectableImageText( image = adaptiveIconPainterResource(id = accountType.iconRes), - text = stringResource(id = accountType.typeName), + text = stringResource(id = accountType.nameRes), style = MaterialTheme.typography.bodyLarge, imageSize = 24.dp, spacing = MaterialTheme.spacing.mediumSpacing, diff --git a/app/src/main/java/com/readrops/app/account/selection/AccountSelectionScreenModel.kt b/app/src/main/java/com/readrops/app/account/selection/AccountSelectionScreenModel.kt index 1e7a1858..968b6b62 100644 --- a/app/src/main/java/com/readrops/app/account/selection/AccountSelectionScreenModel.kt +++ b/app/src/main/java/com/readrops/app/account/selection/AccountSelectionScreenModel.kt @@ -55,8 +55,8 @@ class AccountSelectionScreenModel( val context = get() val account = Account( url = null, - accountName = context.getString(AccountType.LOCAL.typeName), - accountType = AccountType.LOCAL, + name = context.getString(AccountType.LOCAL.nameRes), + type = AccountType.LOCAL, isCurrentAccount = true ) diff --git a/app/src/main/java/com/readrops/app/feeds/FeedState.kt b/app/src/main/java/com/readrops/app/feeds/FeedState.kt index cb00cae6..3530dd61 100644 --- a/app/src/main/java/com/readrops/app/feeds/FeedState.kt +++ b/app/src/main/java/com/readrops/app/feeds/FeedState.kt @@ -41,7 +41,7 @@ sealed class FolderAndFeedsState { data class AddFeedDialogState( val url: String = "", - val selectedAccount: Account = Account(accountName = ""), + val selectedAccount: Account = Account(name = ""), val accounts: List = listOf(), val error: TextFieldError? = null, val exception: Exception? = null, diff --git a/app/src/main/java/com/readrops/app/feeds/dialogs/AddFeedDialog.kt b/app/src/main/java/com/readrops/app/feeds/dialogs/AddFeedDialog.kt index a38e2d1a..34ad7591 100644 --- a/app/src/main/java/com/readrops/app/feeds/dialogs/AddFeedDialog.kt +++ b/app/src/main/java/com/readrops/app/feeds/dialogs/AddFeedDialog.kt @@ -79,14 +79,14 @@ fun AddFeedDialog( ) { for (account in state.accounts) { DropdownMenuItem( - text = { Text(text = account.accountName!!) }, + text = { Text(text = account.name!!) }, onClick = { onAccountClick(account) }, leadingIcon = { Image( painter = adaptiveIconPainterResource( - id = account.accountType!!.iconRes + id = account.type!!.iconRes ), contentDescription = null, modifier = Modifier.size(24.dp) @@ -97,7 +97,7 @@ fun AddFeedDialog( } OutlinedTextField( - value = state.selectedAccount.accountName!!, + value = state.selectedAccount.name!!, readOnly = true, onValueChange = {}, trailingIcon = { @@ -106,7 +106,7 @@ fun AddFeedDialog( leadingIcon = { Image( painter = adaptiveIconPainterResource( - id = state.selectedAccount.accountType!!.iconRes + id = state.selectedAccount.type!!.iconRes ), contentDescription = null, modifier = Modifier.size(24.dp) diff --git a/app/src/main/java/com/readrops/app/item/ItemScreenModel.kt b/app/src/main/java/com/readrops/app/item/ItemScreenModel.kt index 19699e0e..812c2523 100644 --- a/app/src/main/java/com/readrops/app/item/ItemScreenModel.kt +++ b/app/src/main/java/com/readrops/app/item/ItemScreenModel.kt @@ -52,7 +52,7 @@ class ItemScreenModel( .flatMapLatest { account -> this@ItemScreenModel.account = account!! - if (account.accountType == AccountType.FEVER) { + if (account.type == AccountType.FEVER) { get().apply { account.login = getString(account.loginKey, null) account.password = getString(account.passwordKey, null) diff --git a/app/src/main/java/com/readrops/app/repositories/FeverRepository.kt b/app/src/main/java/com/readrops/app/repositories/FeverRepository.kt index 62c1a240..37d15cf1 100644 --- a/app/src/main/java/com/readrops/app/repositories/FeverRepository.kt +++ b/app/src/main/java/com/readrops/app/repositories/FeverRepository.kt @@ -25,7 +25,7 @@ class FeverRepository( val authenticated = feverDataSource.login(account.login!!, account.password!!) if (authenticated) { - account.displayedName = account.accountType!!.name + account.displayedName = account.type!!.name } else { throw LoginFailedException() } diff --git a/app/src/main/java/com/readrops/app/repositories/LocalRSSRepository.kt b/app/src/main/java/com/readrops/app/repositories/LocalRSSRepository.kt index 756a5e23..fc3b6ba5 100644 --- a/app/src/main/java/com/readrops/app/repositories/LocalRSSRepository.kt +++ b/app/src/main/java/com/readrops/app/repositories/LocalRSSRepository.kt @@ -115,7 +115,7 @@ class LocalRSSRepository( private suspend fun insertFeed(feed: Feed): Feed { // TODO better handle this case require(!database.feedDao().feedExists(feed.url!!, account.id)) { - "Feed already exists for account ${account.accountName}" + "Feed already exists for account ${account.name}" } return feed.apply { diff --git a/app/src/main/java/com/readrops/app/sync/SyncAnalyzer.kt b/app/src/main/java/com/readrops/app/sync/SyncAnalyzer.kt index d7749ed3..6c577edf 100644 --- a/app/src/main/java/com/readrops/app/sync/SyncAnalyzer.kt +++ b/app/src/main/java/com/readrops/app/sync/SyncAnalyzer.kt @@ -67,11 +67,11 @@ class SyncAnalyzer( // multiple new items from several feeds feedsIdsForNewItems.size > 1 && itemCount > 1 -> { NotificationContent( - title = account.accountName!!, + title = account.name!!, text = context.getString(R.string.new_items, itemCount.toString()), largeIcon = ContextCompat.getDrawable( context, - account.accountType!!.iconRes + account.type!!.iconRes )!!.toBitmap(), accountId = account.id ) diff --git a/app/src/main/java/com/readrops/app/sync/SyncWorker.kt b/app/src/main/java/com/readrops/app/sync/SyncWorker.kt index 1db5a582..cc64e2c1 100644 --- a/app/src/main/java/com/readrops/app/sync/SyncWorker.kt +++ b/app/src/main/java/com/readrops/app/sync/SyncWorker.kt @@ -131,7 +131,7 @@ class SyncWorker( notificationBuilder.setContentTitle( applicationContext.resources.getString( R.string.updating_account, - account.accountName + account.name ) ) @@ -212,7 +212,7 @@ class SyncWorker( if (result.second.isNotEmpty()) { Log.e( TAG, - "refreshing local account ${account.accountName}: ${result.second.size} errors" + "refreshing local account ${account.name}: ${result.second.size} errors" ) } diff --git a/db/schemas/com.readrops.db.Database/5.json b/db/schemas/com.readrops.db.Database/5.json new file mode 100644 index 00000000..fb815bae --- /dev/null +++ b/db/schemas/com.readrops.db.Database/5.json @@ -0,0 +1,526 @@ +{ + "formatVersion": 1, + "database": { + "version": 5, + "identityHash": "0bac941f8b1b6003c35a6d0cdc1f2e13", + "entities": [ + { + "tableName": "Feed", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `description` TEXT, `url` TEXT, `siteUrl` TEXT, `last_updated` TEXT, `color` INTEGER NOT NULL, `icon_url` TEXT, `etag` TEXT, `last_modified` TEXT, `folder_id` INTEGER, `remote_id` TEXT, `account_id` INTEGER NOT NULL, `notification_enabled` INTEGER NOT NULL DEFAULT 1, FOREIGN KEY(`folder_id`) REFERENCES `Folder`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL , FOREIGN KEY(`account_id`) REFERENCES `Account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "siteUrl", + "columnName": "siteUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastUpdated", + "columnName": "last_updated", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "iconUrl", + "columnName": "icon_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "etag", + "columnName": "etag", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastModified", + "columnName": "last_modified", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "folderId", + "columnName": "folder_id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "remoteId", + "columnName": "remote_id", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "accountId", + "columnName": "account_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isNotificationEnabled", + "columnName": "notification_enabled", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "1" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Feed_folder_id", + "unique": false, + "columnNames": [ + "folder_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Feed_folder_id` ON `${TABLE_NAME}` (`folder_id`)" + }, + { + "name": "index_Feed_account_id", + "unique": false, + "columnNames": [ + "account_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Feed_account_id` ON `${TABLE_NAME}` (`account_id`)" + } + ], + "foreignKeys": [ + { + "table": "Folder", + "onDelete": "SET NULL", + "onUpdate": "NO ACTION", + "columns": [ + "folder_id" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Account", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "account_id" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "Item", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT, `description` TEXT, `clean_description` TEXT, `link` TEXT, `image_link` TEXT, `author` TEXT, `pub_date` INTEGER, `content` TEXT, `feed_id` INTEGER NOT NULL, `read_time` REAL NOT NULL, `read` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `remote_id` TEXT, FOREIGN KEY(`feed_id`) REFERENCES `Feed`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "cleanDescription", + "columnName": "clean_description", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "link", + "columnName": "link", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "imageLink", + "columnName": "image_link", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "author", + "columnName": "author", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "pubDate", + "columnName": "pub_date", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "feedId", + "columnName": "feed_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readTime", + "columnName": "read_time", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "isRead", + "columnName": "read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isStarred", + "columnName": "starred", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "remoteId", + "columnName": "remote_id", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Item_feed_id", + "unique": false, + "columnNames": [ + "feed_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Item_feed_id` ON `${TABLE_NAME}` (`feed_id`)" + } + ], + "foreignKeys": [ + { + "table": "Feed", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "feed_id" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "Folder", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `remoteId` TEXT, `account_id` INTEGER NOT NULL, FOREIGN KEY(`account_id`) REFERENCES `Account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "remoteId", + "columnName": "remoteId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "accountId", + "columnName": "account_id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Folder_account_id", + "unique": false, + "columnNames": [ + "account_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Folder_account_id` ON `${TABLE_NAME}` (`account_id`)" + } + ], + "foreignKeys": [ + { + "table": "Account", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "account_id" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "Account", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT, `name` TEXT, `displayed_name` TEXT, `type` TEXT, `last_modified` INTEGER NOT NULL, `current_account` INTEGER NOT NULL, `token` TEXT, `write_token` TEXT, `notifications_enabled` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "displayedName", + "columnName": "displayed_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastModified", + "columnName": "last_modified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCurrentAccount", + "columnName": "current_account", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "token", + "columnName": "token", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "writeToken", + "columnName": "write_token", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isNotificationsEnabled", + "columnName": "notifications_enabled", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ItemStateChange", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `read_change` INTEGER NOT NULL, `star_change` INTEGER NOT NULL, `account_id` INTEGER NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`account_id`) REFERENCES `Account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readChange", + "columnName": "read_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "starChange", + "columnName": "star_change", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "accountId", + "columnName": "account_id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [ + { + "table": "Account", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "account_id" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "ItemState", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `read` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `remote_id` TEXT NOT NULL, `account_id` INTEGER NOT NULL, FOREIGN KEY(`account_id`) REFERENCES `Account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "read", + "columnName": "read", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "starred", + "columnName": "starred", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "remoteId", + "columnName": "remote_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accountId", + "columnName": "account_id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_ItemState_remote_id_account_id", + "unique": true, + "columnNames": [ + "remote_id", + "account_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_ItemState_remote_id_account_id` ON `${TABLE_NAME}` (`remote_id`, `account_id`)" + } + ], + "foreignKeys": [ + { + "table": "Account", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "account_id" + ], + "referencedColumns": [ + "id" + ] + } + ] + } + ], + "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, '0bac941f8b1b6003c35a6d0cdc1f2e13')" + ] + } +} \ No newline at end of file diff --git a/db/src/androidTest/java/com/readrops/db/MigrationsTest.kt b/db/src/androidTest/java/com/readrops/db/MigrationsTest.kt index f165f641..c32bcf4d 100644 --- a/db/src/androidTest/java/com/readrops/db/MigrationsTest.kt +++ b/db/src/androidTest/java/com/readrops/db/MigrationsTest.kt @@ -54,4 +54,16 @@ class MigrationsTest { assertEquals("guid", remoteId) } } + + @Test + fun migrate4To5() { + helper.createDatabase(dbName, 4).apply { + execSQL("Insert Into Account(account_type, last_modified, current_account, notifications_enabled) Values(0, 0, 1, 0)") + } + + helper.runMigrationsAndValidate(dbName, 5, true, MigrationFrom4To5).apply { + val type = compileStatement("Select type From Account").simpleQueryForString() + assertEquals("LOCAL", type) + } + } } \ No newline at end of file diff --git a/db/src/androidTest/java/com/readrops/db/dao/FeedDaoTest.kt b/db/src/androidTest/java/com/readrops/db/dao/FeedDaoTest.kt index 2941c87f..123dd531 100644 --- a/db/src/androidTest/java/com/readrops/db/dao/FeedDaoTest.kt +++ b/db/src/androidTest/java/com/readrops/db/dao/FeedDaoTest.kt @@ -28,7 +28,7 @@ class FeedDaoTest { val context = ApplicationProvider.getApplicationContext() database = Room.inMemoryDatabaseBuilder(context, Database::class.java).build() - account = Account(accountType = AccountType.LOCAL).apply { + account = Account(type = AccountType.LOCAL).apply { id = database.accountDao().insert(this).toInt() } diff --git a/db/src/androidTest/java/com/readrops/db/dao/FolderDaoTest.kt b/db/src/androidTest/java/com/readrops/db/dao/FolderDaoTest.kt index 9d53e9c4..fd8c8578 100644 --- a/db/src/androidTest/java/com/readrops/db/dao/FolderDaoTest.kt +++ b/db/src/androidTest/java/com/readrops/db/dao/FolderDaoTest.kt @@ -28,7 +28,7 @@ class FolderDaoTest { val context = ApplicationProvider.getApplicationContext() database = Room.inMemoryDatabaseBuilder(context, Database::class.java).build() - account = Account(accountType = AccountType.LOCAL).apply { + account = Account(type = AccountType.LOCAL).apply { id = database.accountDao().insert(this).toInt() } diff --git a/db/src/main/java/com/readrops/db/Database.kt b/db/src/main/java/com/readrops/db/Database.kt index 92c14691..1d69f123 100644 --- a/db/src/main/java/com/readrops/db/Database.kt +++ b/db/src/main/java/com/readrops/db/Database.kt @@ -17,12 +17,13 @@ import com.readrops.db.entities.Item import com.readrops.db.entities.ItemState import com.readrops.db.entities.ItemStateChange import com.readrops.db.entities.account.Account +import com.readrops.db.entities.account.AccountType import com.readrops.db.util.Converters @Database( entities = [Feed::class, Item::class, Folder::class, Account::class, ItemStateChange::class, ItemState::class], - version = 4 + version = 5 ) @TypeConverters(Converters::class) abstract class Database : RoomDatabase() { @@ -96,5 +97,29 @@ object MigrationFrom3To4 : Migration(3, 4) { db.execSQL("CREATE INDEX IF NOT EXISTS `index_Feed_folder_id` ON `Feed` (`folder_id`)") db.execSQL("CREATE INDEX IF NOT EXISTS `index_Feed_account_id` ON `Feed` (`account_id`)") } - +} + +object MigrationFrom4To5 : Migration(4, 5) { + + override fun migrate(db: SupportSQLiteDatabase) { + // rename account_name to name + // rename account_type to type + // rename writeToken to write_token + db.execSQL("CREATE TABLE IF NOT EXISTS `_new_Account` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT, `name` TEXT, `displayed_name` TEXT, `type` TEXT, `last_modified` INTEGER NOT NULL, `current_account` INTEGER NOT NULL, `token` TEXT, `write_token` TEXT, `notifications_enabled` INTEGER NOT NULL)") + db.execSQL("INSERT INTO `_new_Account` (`id`, `url`, `name`, `displayed_name`, `type`, `last_modified`, `current_account`, `token`, `write_token`, `notifications_enabled`) SELECT `id`, `url`, `account_name`, `displayed_name`, NULL, `last_modified`, `current_account`, `token`, `writeToken`, `notifications_enabled` FROM `Account`") + + // migrate type from INTEGER to TEXT + val cursor = db.query("SELECT `id`, `account_type` FROM `Account`") + while (cursor.moveToNext()) { + val id = cursor.getInt(0) + val ordinal = cursor.getInt(1) + + val type = AccountType.entries[ordinal] + + db.execSQL("UPDATE `_new_Account` SET `type` = \"${type.name}\" WHERE `id` = $id") + } + + db.execSQL("DROP TABLE IF EXISTS `Account`") + db.execSQL("ALTER TABLE `_new_Account` RENAME TO `Account`") + } } diff --git a/db/src/main/java/com/readrops/db/DbModule.kt b/db/src/main/java/com/readrops/db/DbModule.kt index 4487f52f..64b7abb6 100644 --- a/db/src/main/java/com/readrops/db/DbModule.kt +++ b/db/src/main/java/com/readrops/db/DbModule.kt @@ -7,7 +7,12 @@ val dbModule = module { single(createdAtStart = true) { Room.databaseBuilder(get(), Database::class.java, "readrops-db") - .addMigrations(MigrationFrom1To2, MigrationFrom2To3, MigrationFrom3To4) + .addMigrations( + MigrationFrom1To2, + MigrationFrom2To3, + MigrationFrom3To4, + MigrationFrom4To5 + ) .build() } } \ No newline at end of file diff --git a/db/src/main/java/com/readrops/db/dao/AccountDao.kt b/db/src/main/java/com/readrops/db/dao/AccountDao.kt index 0abe8a36..98073528 100644 --- a/db/src/main/java/com/readrops/db/dao/AccountDao.kt +++ b/db/src/main/java/com/readrops/db/dao/AccountDao.kt @@ -46,6 +46,6 @@ interface AccountDao : BaseDao { When id Is Not :accountId Then 0 End""") suspend fun updateCurrentAccount(accountId: Int) - @Query("Update Account set account_name = :name Where id = :accountId") + @Query("Update Account set name = :name Where id = :accountId") suspend fun renameAccount(accountId: Int, name: String) } \ No newline at end of file diff --git a/db/src/main/java/com/readrops/db/entities/account/Account.kt b/db/src/main/java/com/readrops/db/entities/account/Account.kt index 31b18461..3e59655e 100644 --- a/db/src/main/java/com/readrops/db/entities/account/Account.kt +++ b/db/src/main/java/com/readrops/db/entities/account/Account.kt @@ -8,34 +8,31 @@ import java.io.Serializable @Entity data class Account( - @PrimaryKey(autoGenerate = true) var id: Int = 0, - var url: String? = null, - @ColumnInfo(name = "account_name") var accountName: String? = null, - @ColumnInfo(name = "displayed_name") var displayedName: String? = null, - @ColumnInfo(name = "account_type") var accountType: AccountType? = null, - @ColumnInfo(name = "last_modified") var lastModified: Long = 0, - @ColumnInfo(name = "current_account") var isCurrentAccount: Boolean = false, - var token: String? = null, - var writeToken: String? = null, // TODO : see if there is a better solution to store specific service account fields - @ColumnInfo(name = "notifications_enabled") var isNotificationsEnabled: Boolean = false, - @Ignore var login: String? = null, - @Ignore var password: String? = null, + @PrimaryKey(autoGenerate = true) var id: Int = 0, + var url: String? = null, + @ColumnInfo(name = "name") var name: String? = null, + @ColumnInfo(name = "displayed_name") var displayedName: String? = null, + @ColumnInfo(name = "type") var type: AccountType? = null, + @ColumnInfo(name = "last_modified") var lastModified: Long = 0, + @ColumnInfo(name = "current_account") var isCurrentAccount: Boolean = false, + var token: String? = null, + @ColumnInfo(name = "write_token") var writeToken: String? = null, + @ColumnInfo(name = "notifications_enabled") var isNotificationsEnabled: Boolean = false, + @Ignore var login: String? = null, + @Ignore var password: String? = null, ) : Serializable { - constructor(accountUrl: String?, accountName: String, accountType: AccountType): - this(url = accountUrl, accountName = accountName, accountType = accountType) - val config: AccountConfig - get() = accountType!!.accountConfig!! + get() = type!!.config val isLocal - get() = accountType == AccountType.LOCAL + get() = type == AccountType.LOCAL - fun `is`(accountType: AccountType) = this.accountType == accountType + fun `is`(accountType: AccountType) = this.type == accountType val loginKey - get() = accountType!!.name + "_login_" + id + get() = type!!.name + "_login_" + id val passwordKey - get() = accountType!!.name + "_password_" + id + get() = type!!.name + "_password_" + id } \ No newline at end of file diff --git a/db/src/main/java/com/readrops/db/entities/account/AccountConfig.kt b/db/src/main/java/com/readrops/db/entities/account/AccountConfig.kt index 0910d6c1..96b1ce73 100644 --- a/db/src/main/java/com/readrops/db/entities/account/AccountConfig.kt +++ b/db/src/main/java/com/readrops/db/entities/account/AccountConfig.kt @@ -5,7 +5,7 @@ data class AccountConfig( val addNoFolder: Boolean, // Add a "No folder" option when modifying a feed's folder val useSeparateState: Boolean, // Let know if it uses ItemState table to synchronize read/star state val canCreateFolder: Boolean, // Enable or disable folder creation in Feed Tab - val canCreateFeed: Boolean = true, + val canCreateFeed: Boolean = true, val canUpdateFolder: Boolean = true, val canUpdateFeed: Boolean = true, val canDeleteFeed: Boolean = true, diff --git a/db/src/main/java/com/readrops/db/entities/account/AccountType.kt b/db/src/main/java/com/readrops/db/entities/account/AccountType.kt index 22d61f46..161f8c8f 100644 --- a/db/src/main/java/com/readrops/db/entities/account/AccountType.kt +++ b/db/src/main/java/com/readrops/db/entities/account/AccountType.kt @@ -4,13 +4,13 @@ import androidx.annotation.DrawableRes import androidx.annotation.StringRes import com.readrops.db.R -// TODO comment Feedly -enum class AccountType(@DrawableRes val iconRes: Int, - @StringRes val typeName: Int, - val accountConfig: AccountConfig?) { +enum class AccountType( + @DrawableRes val iconRes: Int, + @StringRes val nameRes: Int, + val config: AccountConfig +) { LOCAL(R.mipmap.ic_launcher, R.string.local_account, AccountConfig.LOCAL), NEXTCLOUD_NEWS(R.drawable.ic_nextcloud_news, R.string.nextcloud_news, AccountConfig.NEXTCLOUD_NEWS), - FEEDLY(R.drawable.ic_feedly, R.string.feedly, null), FRESHRSS(R.drawable.ic_freshrss, R.string.freshrss, AccountConfig.FRESHRSS), FEVER(R.drawable.ic_fever, R.string.fever, AccountConfig.FEVER) } \ No newline at end of file diff --git a/db/src/main/java/com/readrops/db/util/Converters.kt b/db/src/main/java/com/readrops/db/util/Converters.kt index d0395939..cf42010f 100644 --- a/db/src/main/java/com/readrops/db/util/Converters.kt +++ b/db/src/main/java/com/readrops/db/util/Converters.kt @@ -1,7 +1,6 @@ package com.readrops.db.util import androidx.room.TypeConverter -import com.readrops.db.entities.account.AccountType import java.time.LocalDateTime class Converters { @@ -15,15 +14,4 @@ class Converters { fun fromLocalDateTime(localDateTime: LocalDateTime): Long { return localDateTime.toInstant(DateUtils.defaultOffset).toEpochMilli() } - - // TODO Use Room built-in enum converter, ordinal is not reliable - @TypeConverter - fun fromAccountTypeCode(ordinal: Int): AccountType { - return AccountType.entries[ordinal] - } - - @TypeConverter - fun getAccountTypeCode(accountType: AccountType): Int { - return accountType.ordinal - } } \ No newline at end of file