mirror of https://github.com/readrops/Readrops.git
Migrate database to new version
* Rename some Account fields * Migrate Account.type enum field from INTEGER to TEXT
This commit is contained in:
parent
fb68f1f492
commit
4753454a9d
|
@ -12,9 +12,9 @@ abstract class Credentials(val authorization: String?, val url: String) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun toCredentials(account: Account): Credentials {
|
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.NEXTCLOUD_NEWS -> NextcloudNewsCredentials(account.login, account.password, account.url + endPoint)
|
||||||
AccountType.FRESHRSS -> FreshRSSCredentials(account.token, account.url + endPoint)
|
AccountType.FRESHRSS -> FreshRSSCredentials(account.token, account.url + endPoint)
|
||||||
AccountType.FEVER -> FeverCredentials(account.login, account.password, account.url + endPoint)
|
AccountType.FEVER -> FeverCredentials(account.login, account.password, account.url + endPoint)
|
||||||
|
|
|
@ -22,7 +22,7 @@ class GetFoldersWithFeedsTest {
|
||||||
|
|
||||||
private lateinit var database: Database
|
private lateinit var database: Database
|
||||||
private lateinit var getFoldersWithFeeds: GetFoldersWithFeeds
|
private lateinit var getFoldersWithFeeds: GetFoldersWithFeeds
|
||||||
private val account = Account(accountType = AccountType.LOCAL)
|
private val account = Account(type = AccountType.LOCAL)
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun before() {
|
fun before() {
|
||||||
|
|
|
@ -32,7 +32,7 @@ import kotlin.test.assertTrue
|
||||||
class LocalRSSRepositoryTest : KoinTest {
|
class LocalRSSRepositoryTest : KoinTest {
|
||||||
|
|
||||||
private val mockServer: MockWebServer = MockWebServer()
|
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 database: Database
|
||||||
private lateinit var repository: LocalRSSRepository
|
private lateinit var repository: LocalRSSRepository
|
||||||
private lateinit var feeds: List<Feed>
|
private lateinit var feeds: List<Feed>
|
||||||
|
|
|
@ -32,20 +32,20 @@ class SyncAnalyzerTest {
|
||||||
NullPointerException("Notification content shouldn't be null")
|
NullPointerException("Notification content shouldn't be null")
|
||||||
|
|
||||||
private val account1 = Account(
|
private val account1 = Account(
|
||||||
accountName = "test account 1",
|
name = "test account 1",
|
||||||
accountType = AccountType.FRESHRSS,
|
type = AccountType.FRESHRSS,
|
||||||
isNotificationsEnabled = true
|
isNotificationsEnabled = true
|
||||||
)
|
)
|
||||||
|
|
||||||
private val account2 = Account(
|
private val account2 = Account(
|
||||||
accountName = "test account 2",
|
name = "test account 2",
|
||||||
accountType = AccountType.NEXTCLOUD_NEWS,
|
type = AccountType.NEXTCLOUD_NEWS,
|
||||||
isNotificationsEnabled = false
|
isNotificationsEnabled = false
|
||||||
)
|
)
|
||||||
|
|
||||||
private val account3 = Account(
|
private val account3 = Account(
|
||||||
accountName = "test account 3",
|
name = "test account 3",
|
||||||
accountType = AccountType.LOCAL,
|
type = AccountType.LOCAL,
|
||||||
isNotificationsEnabled = true
|
isNotificationsEnabled = true
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -124,7 +124,7 @@ class SyncAnalyzerTest {
|
||||||
|
|
||||||
syncAnalyzer.getNotificationContent(mapOf(account1 to syncResult))?.let { content ->
|
syncAnalyzer.getNotificationContent(mapOf(account1 to syncResult))?.let { content ->
|
||||||
assertEquals(context.getString(R.string.new_items, 2), content.text)
|
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.largeIcon != null)
|
||||||
assertTrue(content.accountId > 0)
|
assertTrue(content.accountId > 0)
|
||||||
} ?: throw nullContentException
|
} ?: throw nullContentException
|
||||||
|
|
|
@ -59,7 +59,7 @@ val appModule = module {
|
||||||
single { GetFoldersWithFeeds(get()) }
|
single { GetFoldersWithFeeds(get()) }
|
||||||
|
|
||||||
factory<BaseRepository> { (account: Account) ->
|
factory<BaseRepository> { (account: Account) ->
|
||||||
when (account.accountType) {
|
when (account.type) {
|
||||||
AccountType.LOCAL -> LocalRSSRepository(get(), get(), account)
|
AccountType.LOCAL -> LocalRSSRepository(get(), get(), account)
|
||||||
AccountType.FRESHRSS -> FreshRSSRepository(
|
AccountType.FRESHRSS -> FreshRSSRepository(
|
||||||
database = get(),
|
database = get(),
|
||||||
|
|
|
@ -178,8 +178,8 @@ class AccountScreenModel(
|
||||||
fun createLocalAccount() {
|
fun createLocalAccount() {
|
||||||
val context = get<Context>()
|
val context = get<Context>()
|
||||||
val account = Account(
|
val account = Account(
|
||||||
accountName = context.getString(AccountType.LOCAL.typeName),
|
name = context.getString(AccountType.LOCAL.nameRes),
|
||||||
accountType = AccountType.LOCAL,
|
type = AccountType.LOCAL,
|
||||||
isCurrentAccount = true
|
isCurrentAccount = true
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -212,7 +212,7 @@ class AccountScreenModel(
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
data class AccountState(
|
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 dialog: DialogState? = null,
|
||||||
val synchronizationErrors: ErrorResult? = null,
|
val synchronizationErrors: ErrorResult? = null,
|
||||||
val error: Exception? = null,
|
val error: Exception? = null,
|
||||||
|
|
|
@ -192,7 +192,7 @@ object AccountTab : Tab {
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
) {
|
) {
|
||||||
Image(
|
Image(
|
||||||
painter = adaptiveIconPainterResource(id = state.account.accountType!!.iconRes),
|
painter = adaptiveIconPainterResource(id = state.account.type!!.iconRes),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier.size(48.dp)
|
modifier = Modifier.size(48.dp)
|
||||||
)
|
)
|
||||||
|
@ -201,7 +201,7 @@ object AccountTab : Tab {
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
Text(
|
Text(
|
||||||
text = state.account.accountName!!,
|
text = state.account.name!!,
|
||||||
style = MaterialTheme.typography.titleLarge,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
|
@ -224,7 +224,7 @@ object AccountTab : Tab {
|
||||||
ThreeDotsMenu(
|
ThreeDotsMenu(
|
||||||
items = mapOf(1 to stringResource(id = R.string.rename_account)),
|
items = mapOf(1 to stringResource(id = R.string.rename_account)),
|
||||||
onItemClick = {
|
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) {
|
for (account in state.accounts) {
|
||||||
SelectableImageText(
|
SelectableImageText(
|
||||||
image = adaptiveIconPainterResource(id = account.accountType!!.iconRes),
|
image = adaptiveIconPainterResource(id = account.type!!.iconRes),
|
||||||
text = account.accountName!!,
|
text = account.name!!,
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
padding = MaterialTheme.spacing.mediumSpacing,
|
padding = MaterialTheme.spacing.mediumSpacing,
|
||||||
spacing = MaterialTheme.spacing.mediumSpacing,
|
spacing = MaterialTheme.spacing.mediumSpacing,
|
||||||
|
@ -358,8 +358,8 @@ object AccountTab : Tab {
|
||||||
screenModel.createLocalAccount()
|
screenModel.createLocalAccount()
|
||||||
} else {
|
} else {
|
||||||
val account = Account(
|
val account = Account(
|
||||||
accountType = accountType,
|
type = accountType,
|
||||||
accountName = context.resources.getString(accountType.typeName)
|
name = context.resources.getString(accountType.nameRes)
|
||||||
)
|
)
|
||||||
navigator.push(
|
navigator.push(
|
||||||
AccountCredentialsScreen(
|
AccountCredentialsScreen(
|
||||||
|
|
|
@ -117,7 +117,7 @@ class AccountCredentialsScreen(
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
) {
|
) {
|
||||||
Image(
|
Image(
|
||||||
painter = adaptiveIconPainterResource(id = account.accountType!!.iconRes),
|
painter = adaptiveIconPainterResource(id = account.type!!.iconRes),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier.size(48.dp)
|
modifier = Modifier.size(48.dp)
|
||||||
)
|
)
|
||||||
|
@ -125,7 +125,7 @@ class AccountCredentialsScreen(
|
||||||
ShortSpacer()
|
ShortSpacer()
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(id = account.accountType!!.typeName),
|
text = stringResource(id = account.type!!.nameRes),
|
||||||
style = MaterialTheme.typography.headlineMedium
|
style = MaterialTheme.typography.headlineMedium
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -155,7 +155,7 @@ class AccountCredentialsScreen(
|
||||||
state.urlError != null -> {
|
state.urlError != null -> {
|
||||||
Text(text = state.urlError!!.errorText())
|
Text(text = state.urlError!!.errorText())
|
||||||
}
|
}
|
||||||
account.accountType == AccountType.FEVER -> {
|
account.type == AccountType.FEVER -> {
|
||||||
Text(text = stringResource(R.string.provide_full_url))
|
Text(text = stringResource(R.string.provide_full_url))
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
|
@ -211,7 +211,7 @@ class AccountCredentialsScreen(
|
||||||
state.passwordError != null -> {
|
state.passwordError != null -> {
|
||||||
Text(text = state.passwordError!!.errorText())
|
Text(text = state.passwordError!!.errorText())
|
||||||
}
|
}
|
||||||
account.accountType == AccountType.FRESHRSS -> {
|
account.type == AccountType.FRESHRSS -> {
|
||||||
Text(text = stringResource(id = R.string.password_helper))
|
Text(text = stringResource(id = R.string.password_helper))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,14 +28,14 @@ class AccountCredentialsScreenModel(
|
||||||
if (mode == AccountCredentialsScreenMode.EDIT_CREDENTIALS) {
|
if (mode == AccountCredentialsScreenMode.EDIT_CREDENTIALS) {
|
||||||
mutableState.update {
|
mutableState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
name = account.accountName!!,
|
name = account.name!!,
|
||||||
url = account.url!!,
|
url = account.url!!,
|
||||||
login = account.login!!,
|
login = account.login!!,
|
||||||
password = account.password!!
|
password = account.password!!
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} 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(
|
val newAccount = account.copy(
|
||||||
url = normalizedUrl,
|
url = normalizedUrl,
|
||||||
accountName = name,
|
name = name,
|
||||||
login = login,
|
login = login,
|
||||||
password = password,
|
password = password,
|
||||||
accountType = account.accountType,
|
type = account.type,
|
||||||
isCurrentAccount = true
|
isCurrentAccount = true
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -22,11 +22,10 @@ fun AccountSelectionDialog(
|
||||||
onDismiss = onDismiss
|
onDismiss = onDismiss
|
||||||
) {
|
) {
|
||||||
AccountType.entries
|
AccountType.entries
|
||||||
.filter { it != AccountType.FEEDLY }
|
|
||||||
.forEach { type ->
|
.forEach { type ->
|
||||||
SelectableImageText(
|
SelectableImageText(
|
||||||
image = adaptiveIconPainterResource(id = type.iconRes),
|
image = adaptiveIconPainterResource(id = type.iconRes),
|
||||||
text = stringResource(id = type.typeName),
|
text = stringResource(id = type.nameRes),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
spacing = MaterialTheme.spacing.mediumSpacing,
|
spacing = MaterialTheme.spacing.mediumSpacing,
|
||||||
padding = MaterialTheme.spacing.shortSpacing,
|
padding = MaterialTheme.spacing.shortSpacing,
|
||||||
|
|
|
@ -95,8 +95,8 @@ class AccountSelectionScreen : AndroidScreen() {
|
||||||
val accountType =
|
val accountType =
|
||||||
(state.navState as NavState.GoToAccountCredentialsScreen).accountType
|
(state.navState as NavState.GoToAccountCredentialsScreen).accountType
|
||||||
val account = Account(
|
val account = Account(
|
||||||
accountType = accountType,
|
type = accountType,
|
||||||
accountName = stringResource(id = accountType.typeName)
|
name = stringResource(id = accountType.nameRes)
|
||||||
)
|
)
|
||||||
|
|
||||||
navigator.push(
|
navigator.push(
|
||||||
|
@ -159,7 +159,7 @@ class AccountSelectionScreen : AndroidScreen() {
|
||||||
|
|
||||||
SelectableImageText(
|
SelectableImageText(
|
||||||
image = adaptiveIconPainterResource(id = R.mipmap.ic_launcher),
|
image = adaptiveIconPainterResource(id = R.mipmap.ic_launcher),
|
||||||
text = stringResource(id = AccountType.LOCAL.typeName),
|
text = stringResource(id = AccountType.LOCAL.nameRes),
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
spacing = MaterialTheme.spacing.mediumSpacing,
|
spacing = MaterialTheme.spacing.mediumSpacing,
|
||||||
padding = MaterialTheme.spacing.mediumSpacing,
|
padding = MaterialTheme.spacing.mediumSpacing,
|
||||||
|
@ -187,11 +187,10 @@ class AccountSelectionScreen : AndroidScreen() {
|
||||||
|
|
||||||
AccountType.entries
|
AccountType.entries
|
||||||
.filter { it != AccountType.LOCAL }
|
.filter { it != AccountType.LOCAL }
|
||||||
.filter { it != AccountType.FEEDLY }
|
|
||||||
.forEach { accountType ->
|
.forEach { accountType ->
|
||||||
SelectableImageText(
|
SelectableImageText(
|
||||||
image = adaptiveIconPainterResource(id = accountType.iconRes),
|
image = adaptiveIconPainterResource(id = accountType.iconRes),
|
||||||
text = stringResource(id = accountType.typeName),
|
text = stringResource(id = accountType.nameRes),
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
imageSize = 24.dp,
|
imageSize = 24.dp,
|
||||||
spacing = MaterialTheme.spacing.mediumSpacing,
|
spacing = MaterialTheme.spacing.mediumSpacing,
|
||||||
|
|
|
@ -55,8 +55,8 @@ class AccountSelectionScreenModel(
|
||||||
val context = get<Context>()
|
val context = get<Context>()
|
||||||
val account = Account(
|
val account = Account(
|
||||||
url = null,
|
url = null,
|
||||||
accountName = context.getString(AccountType.LOCAL.typeName),
|
name = context.getString(AccountType.LOCAL.nameRes),
|
||||||
accountType = AccountType.LOCAL,
|
type = AccountType.LOCAL,
|
||||||
isCurrentAccount = true
|
isCurrentAccount = true
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ sealed class FolderAndFeedsState {
|
||||||
|
|
||||||
data class AddFeedDialogState(
|
data class AddFeedDialogState(
|
||||||
val url: String = "",
|
val url: String = "",
|
||||||
val selectedAccount: Account = Account(accountName = ""),
|
val selectedAccount: Account = Account(name = ""),
|
||||||
val accounts: List<Account> = listOf(),
|
val accounts: List<Account> = listOf(),
|
||||||
val error: TextFieldError? = null,
|
val error: TextFieldError? = null,
|
||||||
val exception: Exception? = null,
|
val exception: Exception? = null,
|
||||||
|
|
|
@ -79,14 +79,14 @@ fun AddFeedDialog(
|
||||||
) {
|
) {
|
||||||
for (account in state.accounts) {
|
for (account in state.accounts) {
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(text = account.accountName!!) },
|
text = { Text(text = account.name!!) },
|
||||||
onClick = {
|
onClick = {
|
||||||
onAccountClick(account)
|
onAccountClick(account)
|
||||||
},
|
},
|
||||||
leadingIcon = {
|
leadingIcon = {
|
||||||
Image(
|
Image(
|
||||||
painter = adaptiveIconPainterResource(
|
painter = adaptiveIconPainterResource(
|
||||||
id = account.accountType!!.iconRes
|
id = account.type!!.iconRes
|
||||||
),
|
),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier.size(24.dp)
|
modifier = Modifier.size(24.dp)
|
||||||
|
@ -97,7 +97,7 @@ fun AddFeedDialog(
|
||||||
}
|
}
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = state.selectedAccount.accountName!!,
|
value = state.selectedAccount.name!!,
|
||||||
readOnly = true,
|
readOnly = true,
|
||||||
onValueChange = {},
|
onValueChange = {},
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
|
@ -106,7 +106,7 @@ fun AddFeedDialog(
|
||||||
leadingIcon = {
|
leadingIcon = {
|
||||||
Image(
|
Image(
|
||||||
painter = adaptiveIconPainterResource(
|
painter = adaptiveIconPainterResource(
|
||||||
id = state.selectedAccount.accountType!!.iconRes
|
id = state.selectedAccount.type!!.iconRes
|
||||||
),
|
),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier.size(24.dp)
|
modifier = Modifier.size(24.dp)
|
||||||
|
|
|
@ -52,7 +52,7 @@ class ItemScreenModel(
|
||||||
.flatMapLatest { account ->
|
.flatMapLatest { account ->
|
||||||
this@ItemScreenModel.account = account!!
|
this@ItemScreenModel.account = account!!
|
||||||
|
|
||||||
if (account.accountType == AccountType.FEVER) {
|
if (account.type == AccountType.FEVER) {
|
||||||
get<SharedPreferences>().apply {
|
get<SharedPreferences>().apply {
|
||||||
account.login = getString(account.loginKey, null)
|
account.login = getString(account.loginKey, null)
|
||||||
account.password = getString(account.passwordKey, null)
|
account.password = getString(account.passwordKey, null)
|
||||||
|
|
|
@ -25,7 +25,7 @@ class FeverRepository(
|
||||||
val authenticated = feverDataSource.login(account.login!!, account.password!!)
|
val authenticated = feverDataSource.login(account.login!!, account.password!!)
|
||||||
|
|
||||||
if (authenticated) {
|
if (authenticated) {
|
||||||
account.displayedName = account.accountType!!.name
|
account.displayedName = account.type!!.name
|
||||||
} else {
|
} else {
|
||||||
throw LoginFailedException()
|
throw LoginFailedException()
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,7 +115,7 @@ class LocalRSSRepository(
|
||||||
private suspend fun insertFeed(feed: Feed): Feed {
|
private suspend fun insertFeed(feed: Feed): Feed {
|
||||||
// TODO better handle this case
|
// TODO better handle this case
|
||||||
require(!database.feedDao().feedExists(feed.url!!, account.id)) {
|
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 {
|
return feed.apply {
|
||||||
|
|
|
@ -67,11 +67,11 @@ class SyncAnalyzer(
|
||||||
// multiple new items from several feeds
|
// multiple new items from several feeds
|
||||||
feedsIdsForNewItems.size > 1 && itemCount > 1 -> {
|
feedsIdsForNewItems.size > 1 && itemCount > 1 -> {
|
||||||
NotificationContent(
|
NotificationContent(
|
||||||
title = account.accountName!!,
|
title = account.name!!,
|
||||||
text = context.getString(R.string.new_items, itemCount.toString()),
|
text = context.getString(R.string.new_items, itemCount.toString()),
|
||||||
largeIcon = ContextCompat.getDrawable(
|
largeIcon = ContextCompat.getDrawable(
|
||||||
context,
|
context,
|
||||||
account.accountType!!.iconRes
|
account.type!!.iconRes
|
||||||
)!!.toBitmap(),
|
)!!.toBitmap(),
|
||||||
accountId = account.id
|
accountId = account.id
|
||||||
)
|
)
|
||||||
|
|
|
@ -131,7 +131,7 @@ class SyncWorker(
|
||||||
notificationBuilder.setContentTitle(
|
notificationBuilder.setContentTitle(
|
||||||
applicationContext.resources.getString(
|
applicationContext.resources.getString(
|
||||||
R.string.updating_account,
|
R.string.updating_account,
|
||||||
account.accountName
|
account.name
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -212,7 +212,7 @@ class SyncWorker(
|
||||||
if (result.second.isNotEmpty()) {
|
if (result.second.isNotEmpty()) {
|
||||||
Log.e(
|
Log.e(
|
||||||
TAG,
|
TAG,
|
||||||
"refreshing local account ${account.accountName}: ${result.second.size} errors"
|
"refreshing local account ${account.name}: ${result.second.size} errors"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -54,4 +54,16 @@ class MigrationsTest {
|
||||||
assertEquals("guid", remoteId)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -28,7 +28,7 @@ class FeedDaoTest {
|
||||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||||
database = Room.inMemoryDatabaseBuilder(context, Database::class.java).build()
|
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()
|
id = database.accountDao().insert(this).toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ class FolderDaoTest {
|
||||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||||
database = Room.inMemoryDatabaseBuilder(context, Database::class.java).build()
|
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()
|
id = database.accountDao().insert(this).toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,12 +17,13 @@ import com.readrops.db.entities.Item
|
||||||
import com.readrops.db.entities.ItemState
|
import com.readrops.db.entities.ItemState
|
||||||
import com.readrops.db.entities.ItemStateChange
|
import com.readrops.db.entities.ItemStateChange
|
||||||
import com.readrops.db.entities.account.Account
|
import com.readrops.db.entities.account.Account
|
||||||
|
import com.readrops.db.entities.account.AccountType
|
||||||
import com.readrops.db.util.Converters
|
import com.readrops.db.util.Converters
|
||||||
|
|
||||||
@Database(
|
@Database(
|
||||||
entities = [Feed::class, Item::class, Folder::class, Account::class,
|
entities = [Feed::class, Item::class, Folder::class, Account::class,
|
||||||
ItemStateChange::class, ItemState::class],
|
ItemStateChange::class, ItemState::class],
|
||||||
version = 4
|
version = 5
|
||||||
)
|
)
|
||||||
@TypeConverters(Converters::class)
|
@TypeConverters(Converters::class)
|
||||||
abstract class Database : RoomDatabase() {
|
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_folder_id` ON `Feed` (`folder_id`)")
|
||||||
db.execSQL("CREATE INDEX IF NOT EXISTS `index_Feed_account_id` ON `Feed` (`account_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`")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,12 @@ val dbModule = module {
|
||||||
|
|
||||||
single(createdAtStart = true) {
|
single(createdAtStart = true) {
|
||||||
Room.databaseBuilder(get(), Database::class.java, "readrops-db")
|
Room.databaseBuilder(get(), Database::class.java, "readrops-db")
|
||||||
.addMigrations(MigrationFrom1To2, MigrationFrom2To3, MigrationFrom3To4)
|
.addMigrations(
|
||||||
|
MigrationFrom1To2,
|
||||||
|
MigrationFrom2To3,
|
||||||
|
MigrationFrom3To4,
|
||||||
|
MigrationFrom4To5
|
||||||
|
)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -46,6 +46,6 @@ interface AccountDao : BaseDao<Account> {
|
||||||
When id Is Not :accountId Then 0 End""")
|
When id Is Not :accountId Then 0 End""")
|
||||||
suspend fun updateCurrentAccount(accountId: Int)
|
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)
|
suspend fun renameAccount(accountId: Int, name: String)
|
||||||
}
|
}
|
|
@ -8,34 +8,31 @@ import java.io.Serializable
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
data class Account(
|
data class Account(
|
||||||
@PrimaryKey(autoGenerate = true) var id: Int = 0,
|
@PrimaryKey(autoGenerate = true) var id: Int = 0,
|
||||||
var url: String? = null,
|
var url: String? = null,
|
||||||
@ColumnInfo(name = "account_name") var accountName: String? = null,
|
@ColumnInfo(name = "name") var name: String? = null,
|
||||||
@ColumnInfo(name = "displayed_name") var displayedName: String? = null,
|
@ColumnInfo(name = "displayed_name") var displayedName: String? = null,
|
||||||
@ColumnInfo(name = "account_type") var accountType: AccountType? = null,
|
@ColumnInfo(name = "type") var type: AccountType? = null,
|
||||||
@ColumnInfo(name = "last_modified") var lastModified: Long = 0,
|
@ColumnInfo(name = "last_modified") var lastModified: Long = 0,
|
||||||
@ColumnInfo(name = "current_account") var isCurrentAccount: Boolean = false,
|
@ColumnInfo(name = "current_account") var isCurrentAccount: Boolean = false,
|
||||||
var token: String? = null,
|
var token: String? = null,
|
||||||
var writeToken: String? = null, // TODO : see if there is a better solution to store specific service account fields
|
@ColumnInfo(name = "write_token") var writeToken: String? = null,
|
||||||
@ColumnInfo(name = "notifications_enabled") var isNotificationsEnabled: Boolean = false,
|
@ColumnInfo(name = "notifications_enabled") var isNotificationsEnabled: Boolean = false,
|
||||||
@Ignore var login: String? = null,
|
@Ignore var login: String? = null,
|
||||||
@Ignore var password: String? = null,
|
@Ignore var password: String? = null,
|
||||||
) : Serializable {
|
) : Serializable {
|
||||||
|
|
||||||
constructor(accountUrl: String?, accountName: String, accountType: AccountType):
|
|
||||||
this(url = accountUrl, accountName = accountName, accountType = accountType)
|
|
||||||
|
|
||||||
val config: AccountConfig
|
val config: AccountConfig
|
||||||
get() = accountType!!.accountConfig!!
|
get() = type!!.config
|
||||||
|
|
||||||
val isLocal
|
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
|
val loginKey
|
||||||
get() = accountType!!.name + "_login_" + id
|
get() = type!!.name + "_login_" + id
|
||||||
|
|
||||||
val passwordKey
|
val passwordKey
|
||||||
get() = accountType!!.name + "_password_" + id
|
get() = type!!.name + "_password_" + id
|
||||||
}
|
}
|
|
@ -5,7 +5,7 @@ data class AccountConfig(
|
||||||
val addNoFolder: Boolean, // Add a "No folder" option when modifying a feed's folder
|
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 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 canCreateFolder: Boolean, // Enable or disable folder creation in Feed Tab
|
||||||
val canCreateFeed: Boolean = true,
|
val canCreateFeed: Boolean = true,
|
||||||
val canUpdateFolder: Boolean = true,
|
val canUpdateFolder: Boolean = true,
|
||||||
val canUpdateFeed: Boolean = true,
|
val canUpdateFeed: Boolean = true,
|
||||||
val canDeleteFeed: Boolean = true,
|
val canDeleteFeed: Boolean = true,
|
||||||
|
|
|
@ -4,13 +4,13 @@ import androidx.annotation.DrawableRes
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import com.readrops.db.R
|
import com.readrops.db.R
|
||||||
|
|
||||||
// TODO comment Feedly
|
enum class AccountType(
|
||||||
enum class AccountType(@DrawableRes val iconRes: Int,
|
@DrawableRes val iconRes: Int,
|
||||||
@StringRes val typeName: Int,
|
@StringRes val nameRes: Int,
|
||||||
val accountConfig: AccountConfig?) {
|
val config: AccountConfig
|
||||||
|
) {
|
||||||
LOCAL(R.mipmap.ic_launcher, R.string.local_account, AccountConfig.LOCAL),
|
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),
|
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),
|
FRESHRSS(R.drawable.ic_freshrss, R.string.freshrss, AccountConfig.FRESHRSS),
|
||||||
FEVER(R.drawable.ic_fever, R.string.fever, AccountConfig.FEVER)
|
FEVER(R.drawable.ic_fever, R.string.fever, AccountConfig.FEVER)
|
||||||
}
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
package com.readrops.db.util
|
package com.readrops.db.util
|
||||||
|
|
||||||
import androidx.room.TypeConverter
|
import androidx.room.TypeConverter
|
||||||
import com.readrops.db.entities.account.AccountType
|
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
class Converters {
|
class Converters {
|
||||||
|
@ -15,15 +14,4 @@ class Converters {
|
||||||
fun fromLocalDateTime(localDateTime: LocalDateTime): Long {
|
fun fromLocalDateTime(localDateTime: LocalDateTime): Long {
|
||||||
return localDateTime.toInstant(DateUtils.defaultOffset).toEpochMilli()
|
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
|
|
||||||
}
|
|
||||||
}
|
}
|
Loading…
Reference in New Issue