From bfedbbe53c9c2d7963a831f28ccb1f5c718170b6 Mon Sep 17 00:00:00 2001 From: Josiah Campbell <9521010+jocmp@users.noreply.github.com> Date: Tue, 2 Jan 2024 22:19:42 -0600 Subject: [PATCH] Remove local feed --- .../ui/accounts/AccountViewModel.kt | 24 +++++++++---- .../ui/articles/ArticleFilterNavigationBar.kt | 6 ++-- .../basilreader/ui/articles/ArticleScreen.kt | 36 ++++++++++++++----- .../jocmp/basilreader/ui/articles/FeedList.kt | 14 ++++---- .../jocmp/basilreader/ui/articles/FeedRow.kt | 6 ++-- .../basilreader/ui/articles/FolderRow.kt | 9 +++-- .../src/main/java/com/jocmp/basil/Account.kt | 30 +++++++++++----- .../java/com/jocmp/basil/AccountUnreadExt.kt | 4 +-- .../java/com/jocmp/basil/opml/OPMLHandler.kt | 2 +- .../jocmp/basil/persistence/FeedRecords.kt | 4 +++ .../sqldelight/com/jocmp/basil/db/feeds.sq | 8 +++++ .../test/java/com/jocmp/basil/AccountTest.kt | 24 ++++++++++++- 12 files changed, 124 insertions(+), 43 deletions(-) diff --git a/app/src/main/java/com/jocmp/basilreader/ui/accounts/AccountViewModel.kt b/app/src/main/java/com/jocmp/basilreader/ui/accounts/AccountViewModel.kt index cfd215c8..c9e7a603 100644 --- a/app/src/main/java/com/jocmp/basilreader/ui/accounts/AccountViewModel.kt +++ b/app/src/main/java/com/jocmp/basilreader/ui/accounts/AccountViewModel.kt @@ -71,18 +71,30 @@ class AccountViewModel( pager.value = account.buildPager(nextFilter) } - fun selectFeed(feedID: String, onComplete: () -> Unit) { + fun selectFeed(feedID: String) { val feed = account.findFeed(feedID) ?: return val feedFilter = ArticleFilter.Feeds(feed = feed, status = filter.value.status) - selectFilter(feedFilter, onComplete) + selectFilter(feedFilter) } - fun selectFolder(title: String, onComplete: () -> Unit) { + fun selectFolder(title: String) { val folder = account.findFolder(title) ?: return val feedFilter = ArticleFilter.Folders(folder = folder, status = filter.value.status) - selectFilter(feedFilter, onComplete) + selectFilter(feedFilter) + } + + fun removeFeed() { + viewModelScope.launch { + val currentFilter = filter.value + + if (currentFilter is ArticleFilter.Feeds) { + account.removeFeed(feedID = currentFilter.feed.id) + selectFilter(ArticleFilter.default().copy(currentFilter.status)) + accountState.value = account + } + } } suspend fun refreshFeed() { @@ -130,13 +142,11 @@ class AccountViewModel( articleState.value = null } - private fun selectFilter(nextFilter: ArticleFilter, onComplete: () -> Unit) { + private fun selectFilter(nextFilter: ArticleFilter) { filter.value = nextFilter pager.value = account.buildPager(nextFilter) clearArticle() - - onComplete() } // private fun selectSettingsAccount(accountID: String) { diff --git a/app/src/main/java/com/jocmp/basilreader/ui/articles/ArticleFilterNavigationBar.kt b/app/src/main/java/com/jocmp/basilreader/ui/articles/ArticleFilterNavigationBar.kt index 4176b517..4f98db1c 100644 --- a/app/src/main/java/com/jocmp/basilreader/ui/articles/ArticleFilterNavigationBar.kt +++ b/app/src/main/java/com/jocmp/basilreader/ui/articles/ArticleFilterNavigationBar.kt @@ -33,7 +33,7 @@ fun ArticleFilterNavigationBar( ) }, label = { Text(stringResource(id = R.string.article_filters_starred)) }, - selected = selected === ArticleStatus.STARRED, + selected = selected == ArticleStatus.STARRED, onClick = { checkedSelect(ArticleStatus.STARRED) }, alwaysShowLabel = false ) @@ -45,7 +45,7 @@ fun ArticleFilterNavigationBar( ) }, label = { Text(stringResource(R.string.article_filters_unread)) }, - selected = selected === ArticleStatus.UNREAD, + selected = selected == ArticleStatus.UNREAD, onClick = { checkedSelect(ArticleStatus.UNREAD) }, alwaysShowLabel = false ) @@ -57,7 +57,7 @@ fun ArticleFilterNavigationBar( ) }, label = { Text(stringResource(R.string.article_filters_all)) }, - selected = selected === ArticleStatus.ALL, + selected = selected == ArticleStatus.ALL, onClick = { checkedSelect(ArticleStatus.ALL) }, alwaysShowLabel = false ) diff --git a/app/src/main/java/com/jocmp/basilreader/ui/articles/ArticleScreen.kt b/app/src/main/java/com/jocmp/basilreader/ui/articles/ArticleScreen.kt index 704808d2..0506a619 100644 --- a/app/src/main/java/com/jocmp/basilreader/ui/articles/ArticleScreen.kt +++ b/app/src/main/java/com/jocmp/basilreader/ui/articles/ArticleScreen.kt @@ -2,10 +2,11 @@ package com.jocmp.basilreader.ui.articles import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.padding -import androidx.compose.material.Button import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.IconButton import androidx.compose.material.TopAppBar import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Menu import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.pullRefresh @@ -24,7 +25,6 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.jocmp.basilreader.ui.accounts.AccountViewModel import kotlinx.coroutines.launch import org.koin.androidx.compose.koinViewModel @@ -74,18 +74,38 @@ fun ArticleScreen( folders = viewModel.folders, feeds = viewModel.feeds, onFeedAdd = onFeedAdd, - onFolderSelect = { viewModel.selectFolder(it) { onComplete() } }, - onFeedSelect = { viewModel.selectFeed(it) { onComplete() } } + onSelectFolder = { + viewModel.selectFolder(it) + onComplete() + }, + onSelectFeed = { + viewModel.selectFeed(it) + onComplete() + }, ) }, listPane = { Scaffold( topBar = { - TopAppBar { - Button(onClick = { coroutineScope.launch { drawerState.open() } }) { - Icon(imageVector = Icons.Filled.Menu, contentDescription = null) + TopAppBar( + title = {}, + navigationIcon = { + IconButton(onClick = { coroutineScope.launch { drawerState.open() } }) { + Icon( + imageVector = Icons.Filled.Menu, + contentDescription = null + ) + } + }, + actions = { + IconButton(onClick = { viewModel.removeFeed() }) { + Icon( + imageVector = Icons.Filled.Delete, + contentDescription = null + ) + } } - } + ) }, bottomBar = { ArticleFilterNavigationBar( diff --git a/app/src/main/java/com/jocmp/basilreader/ui/articles/FeedList.kt b/app/src/main/java/com/jocmp/basilreader/ui/articles/FeedList.kt index da2c0730..3bb33a8e 100644 --- a/app/src/main/java/com/jocmp/basilreader/ui/articles/FeedList.kt +++ b/app/src/main/java/com/jocmp/basilreader/ui/articles/FeedList.kt @@ -21,8 +21,8 @@ fun FeedList( folders: List = emptyList(), feeds: List = emptyList(), onFeedAdd: () -> Unit, - onFolderSelect: (folderTitle: String) -> Unit, - onFeedSelect: (feedID: String) -> Unit + onSelectFolder: (folderTitle: String) -> Unit, + onSelectFeed: (feedID: String) -> Unit, ) { Column { Row { @@ -33,8 +33,8 @@ fun FeedList( folders.forEach { FolderRow( folder = it, - onFolderSelect = onFolderSelect, - onFeedSelect = onFeedSelect + onFolderSelect = onSelectFolder, + onFeedSelect = onSelectFeed, ) } if (feeds.isNotEmpty()) { @@ -44,7 +44,7 @@ fun FeedList( feeds.forEach { FeedRow( feed = it, - onSelect = onFeedSelect + onSelect = onSelectFeed ) } } @@ -61,7 +61,7 @@ fun FeedListPreview() { folders = folders, feeds = feeds, onFeedAdd = {}, - onFolderSelect = {}, - onFeedSelect = {} + onSelectFolder = {}, + onSelectFeed = {} ) } diff --git a/app/src/main/java/com/jocmp/basilreader/ui/articles/FeedRow.kt b/app/src/main/java/com/jocmp/basilreader/ui/articles/FeedRow.kt index 23ccab39..85152850 100644 --- a/app/src/main/java/com/jocmp/basilreader/ui/articles/FeedRow.kt +++ b/app/src/main/java/com/jocmp/basilreader/ui/articles/FeedRow.kt @@ -2,6 +2,7 @@ package com.jocmp.basilreader.ui.articles import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -25,13 +26,14 @@ import com.jocmp.basilreader.ui.theme.BasilReaderTheme @Composable fun FeedRow( feed: Feed, - onSelect: (id: String) -> Unit + onSelect: (id: String) -> Unit, ) { NavigationDrawerItem( label = { Row( horizontalArrangement = Arrangement.SpaceBetween, - modifier = Modifier.fillMaxWidth() + modifier = Modifier + .fillMaxWidth() ) { Text(feed.name) Text(feed.unreadCount.toString()) diff --git a/app/src/main/java/com/jocmp/basilreader/ui/articles/FolderRow.kt b/app/src/main/java/com/jocmp/basilreader/ui/articles/FolderRow.kt index 60a873b3..5f457d92 100644 --- a/app/src/main/java/com/jocmp/basilreader/ui/articles/FolderRow.kt +++ b/app/src/main/java/com/jocmp/basilreader/ui/articles/FolderRow.kt @@ -21,7 +21,7 @@ import com.jocmp.basilreader.ui.theme.BasilReaderTheme fun FolderRow( folder: Folder, onFolderSelect: (folderTitle: String) -> Unit, - onFeedSelect: (feedID: String) -> Unit + onFeedSelect: (feedID: String) -> Unit, ) { Column { Row( @@ -39,7 +39,10 @@ fun FolderRow( } folder.feeds.forEach { feed -> Row { - FeedRow(feed = feed, onSelect = onFeedSelect) + FeedRow( + feed = feed, + onSelect = onFeedSelect, + ) } } } @@ -54,7 +57,7 @@ fun FolderRowPreview() { FolderRow( folder = folder, onFolderSelect = {}, - onFeedSelect = {} + onFeedSelect = {}, ) } } diff --git a/basil/src/main/java/com/jocmp/basil/Account.kt b/basil/src/main/java/com/jocmp/basil/Account.kt index 0658de0a..4528bd4f 100644 --- a/basil/src/main/java/com/jocmp/basil/Account.kt +++ b/basil/src/main/java/com/jocmp/basil/Account.kt @@ -10,7 +10,6 @@ import com.jocmp.basil.opml.asFeed import com.jocmp.basil.opml.asFolder import com.jocmp.basil.persistence.ArticleRecords import com.jocmp.basil.persistence.FeedRecords -import com.jocmp.basil.persistence.articleMapper import com.jocmp.basil.shared.nowUTCInSeconds import com.jocmp.feedfinder.FeedFinder import kotlinx.coroutines.Dispatchers @@ -45,7 +44,8 @@ data class Account( account = this, ) - internal val articles: ArticleRecords = ArticleRecords(database) + internal val articleRecords: ArticleRecords = ArticleRecords(database) + private val feedRecords: FeedRecords = FeedRecords(database) val displayName = "Local" @@ -69,6 +69,20 @@ data class Account( return folder } + suspend fun removeFeed(feedID: String) { + feedRecords.deleteFeed(feedID = feedID) + + feeds.removeIf { it.id == feedID } + + folders.forEach { folder -> + folder.feeds.removeIf { it.id == feedID } + } + + folders.removeIf { it.feeds.isEmpty() } + + saveOPMLFile() + } + suspend fun addFeed(entry: FeedFormEntry): Result { val result = FeedFinder.find(feedURL = entry.url) @@ -80,7 +94,7 @@ data class Account( val externalFeed = delegate.createFeed(feedURL = found.feedURL) - val record = FeedRecords(database).findOrCreate(externalFeed) + val record = feedRecords.findOrCreate(externalFeed) val feed = Feed( id = record.id.toString(), @@ -143,23 +157,23 @@ data class Account( } fun findArticle(articleID: String): Article? { - return articles.fetch(articleID = articleID) + return articleRecords.fetch(articleID = articleID) } fun addStar(articleID: String) { - articles.addStar(articleID = articleID) + articleRecords.addStar(articleID = articleID) } fun removeStar(articleID: String) { - articles.removeStar(articleID = articleID) + articleRecords.removeStar(articleID = articleID) } fun markRead(articleID: String) { - articles.markRead(articleID) + articleRecords.markRead(articleID) } fun markUnread(articleID: String) { - articles.markUnread(articleID = articleID) + articleRecords.markUnread(articleID = articleID) } private fun updateArticles(feed: Feed, items: List) { diff --git a/basil/src/main/java/com/jocmp/basil/AccountUnreadExt.kt b/basil/src/main/java/com/jocmp/basil/AccountUnreadExt.kt index 90493fe9..b5392519 100644 --- a/basil/src/main/java/com/jocmp/basil/AccountUnreadExt.kt +++ b/basil/src/main/java/com/jocmp/basil/AccountUnreadExt.kt @@ -1,6 +1,4 @@ package com.jocmp.basil -import com.jocmp.basil.persistence.ArticleRecords - val Account.unreadCounts: Map - get() = articles.countUnread() + get() = articleRecords.countUnread() diff --git a/basil/src/main/java/com/jocmp/basil/opml/OPMLHandler.kt b/basil/src/main/java/com/jocmp/basil/opml/OPMLHandler.kt index f6f8380e..f382335e 100644 --- a/basil/src/main/java/com/jocmp/basil/opml/OPMLHandler.kt +++ b/basil/src/main/java/com/jocmp/basil/opml/OPMLHandler.kt @@ -81,7 +81,7 @@ internal class OPMLHandler : DefaultHandler() { } currentFolder = null currentType = null - } else if (currentType === OutlineType.FEED && currentFolder != null) { + } else if (currentType == OutlineType.FEED && currentFolder != null) { currentType = OutlineType.FOLDER } else { currentType = null diff --git a/basil/src/main/java/com/jocmp/basil/persistence/FeedRecords.kt b/basil/src/main/java/com/jocmp/basil/persistence/FeedRecords.kt index a3040b63..c206d6b5 100644 --- a/basil/src/main/java/com/jocmp/basil/persistence/FeedRecords.kt +++ b/basil/src/main/java/com/jocmp/basil/persistence/FeedRecords.kt @@ -20,4 +20,8 @@ internal class FeedRecords(val database: Database) { feed_url = externalFeed.feedURL ).executeAsOne() } + + internal fun deleteFeed(feedID: String) { + database.feedsQueries.delete(feedID = feedID.toLong()) + } } diff --git a/basil/src/main/sqldelight/com/jocmp/basil/db/feeds.sq b/basil/src/main/sqldelight/com/jocmp/basil/db/feeds.sq index 69bdf70b..97f0d367 100644 --- a/basil/src/main/sqldelight/com/jocmp/basil/db/feeds.sq +++ b/basil/src/main/sqldelight/com/jocmp/basil/db/feeds.sq @@ -17,3 +17,11 @@ SELECT * FROM feeds WHERE id = last_insert_rowid(); } + +delete { + DELETE FROM article_statuses WHERE article_statuses.feed_id = :feedID; + + DELETE FROM articles WHERE articles.feed_id = :feedID; + + DELETE FROM feeds WHERE id = :feedID; +} diff --git a/basil/src/test/java/com/jocmp/basil/AccountTest.kt b/basil/src/test/java/com/jocmp/basil/AccountTest.kt index c03747fd..6c034c09 100644 --- a/basil/src/test/java/com/jocmp/basil/AccountTest.kt +++ b/basil/src/test/java/com/jocmp/basil/AccountTest.kt @@ -39,7 +39,7 @@ class AccountTest { database = InMemoryDatabaseProvider.forAccount("777") } - private fun buildAccount(id: String, path: File): Account { + private fun buildAccount(id: String = "777", path: File = folder.newFile()): Account { return Account( id = id, path = path.toURI(), @@ -166,6 +166,28 @@ class AccountTest { assertEquals(techFeed, cultureFeed) } + @Test + fun removeFeed_topLevelFeed() { + val account = buildAccount() + runBlocking { + account.addFeed( + FeedFormEntry( + url = "https://theverge.com/rss/index.xml", + name = "The Verge", + folderTitles = listOf(), + ) + ) + } + + val feed = account.feeds.find { it.name == "The Verge" }!! + + assertEquals(expected = 1, account.feeds.size) + + runBlocking { account.removeFeed(feedID = feed.id) } + + assertEquals(expected = 0, account.feeds.size) + } + @Test fun findFeed_topLevelFeed() { val account = buildAccount(id = "777", path = folder.newFile())