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 f8ee75ba..b27b9cd8 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
@@ -89,7 +89,7 @@ class AccountViewModel(
fun selectArticle(articleID: String) {
account.markRead(articleID)
- articleState.value = account.findArticle(articleID.toLong())
+ articleState.value = account.findArticle(articleID = articleID)
}
fun toggleArticleRead() {
@@ -104,6 +104,18 @@ class AccountViewModel(
}
}
+ fun toggleArticleStar() {
+ articleState.value?.let { article ->
+ if (article.starred) {
+ account.removeStar(article.id)
+ } else {
+ account.addStar(article.id)
+ }
+
+ articleState.value = article.copy(starred = !article.starred)
+ }
+ }
+
fun clearArticle() {
articleState.value = null
}
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 c70ea6ab..4176b517 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
@@ -26,7 +26,12 @@ fun ArticleFilterNavigationBar(
NavigationBar {
NavigationBarItem(
- icon = { Icon(Icons.Filled.Favorite, contentDescription = null) },
+ icon = {
+ Icon(
+ painterResource(R.drawable.icon_star_filled),
+ contentDescription = null
+ )
+ },
label = { Text(stringResource(id = R.string.article_filters_starred)) },
selected = selected === ArticleStatus.STARRED,
onClick = { checkedSelect(ArticleStatus.STARRED) },
@@ -35,7 +40,7 @@ fun ArticleFilterNavigationBar(
NavigationBarItem(
icon = {
Icon(
- painter = painterResource(R.drawable.unread),
+ painterResource(R.drawable.icon_circle_filled),
contentDescription = null
)
},
@@ -47,7 +52,7 @@ fun ArticleFilterNavigationBar(
NavigationBarItem(
icon = {
Icon(
- painter = painterResource(R.drawable.notes),
+ painter = painterResource(R.drawable.icon_notes),
contentDescription = null
)
},
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 0cb9bb2e..a81da4d7 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
@@ -113,6 +113,7 @@ fun ArticleScreen(
ArticleView(
article = viewModel.article,
onToggleRead = viewModel::toggleArticleRead,
+ onToggleStar = viewModel::toggleArticleStar,
onBackPressed = {
viewModel.clearArticle()
setDestination(ListDetailPaneScaffoldRole.List)
diff --git a/app/src/main/java/com/jocmp/basilreader/ui/articles/ArticleView.kt b/app/src/main/java/com/jocmp/basilreader/ui/articles/ArticleView.kt
index 9d9de2b3..056c00bd 100644
--- a/app/src/main/java/com/jocmp/basilreader/ui/articles/ArticleView.kt
+++ b/app/src/main/java/com/jocmp/basilreader/ui/articles/ArticleView.kt
@@ -2,18 +2,22 @@ package com.jocmp.basilreader.ui.articles
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CheckCircle
-import androidx.compose.material.icons.filled.Phone
+import androidx.compose.material.icons.filled.Star
import androidx.compose.material.icons.outlined.CheckCircle
-import androidx.compose.material.icons.outlined.Phone
+import androidx.compose.material.icons.outlined.Star
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
import com.jocmp.basil.Article
+import com.jocmp.basilreader.R
import com.jocmp.basilreader.ui.components.EmptyView
import com.jocmp.basilreader.ui.components.WebView
import com.jocmp.basilreader.ui.components.rememberWebViewStateWithHTMLData
@@ -23,11 +27,13 @@ fun ArticleView(
article: Article?,
onBackPressed: () -> Unit,
onToggleRead: () -> Unit,
+ onToggleStar: () -> Unit
) {
if (article != null) {
ArticleLoadedView(
article = article,
- onToggleRead = onToggleRead
+ onToggleRead = onToggleRead,
+ onToggleStar = onToggleStar
)
} else {
EmptyView()
@@ -42,19 +48,31 @@ fun ArticleView(
fun ArticleLoadedView(
article: Article,
onToggleRead: () -> Unit,
+ onToggleStar: () -> Unit
) {
val state = rememberWebViewStateWithHTMLData(article.contentHTML)
- val image = if (article.read) {
- Icons.Outlined.CheckCircle
+ val readIcon = if (article.read) {
+ R.drawable.icon_circle_outline
} else {
- Icons.Filled.CheckCircle
+ R.drawable.icon_circle_filled
+ }
+
+ val starIcon = if (article.starred) {
+ R.drawable.icon_star_filled
+ } else {
+ R.drawable.icon_star_outline
}
Scaffold(
topBar = {
- Button(onClick = { onToggleRead() }) {
- Icon(imageVector = image, contentDescription = null)
+ Row {
+ IconButton(onClick = { onToggleRead() }) {
+ Icon(painterResource(id = readIcon), contentDescription = null)
+ }
+ IconButton(onClick = { onToggleStar() }) {
+ Icon(painterResource(id = starIcon), contentDescription = null)
+ }
}
}
) { innerPadding ->
diff --git a/app/src/main/res/drawable/unread.xml b/app/src/main/res/drawable/icon_circle_filled.xml
similarity index 89%
rename from app/src/main/res/drawable/unread.xml
rename to app/src/main/res/drawable/icon_circle_filled.xml
index e7b84944..92d73bf8 100644
--- a/app/src/main/res/drawable/unread.xml
+++ b/app/src/main/res/drawable/icon_circle_filled.xml
@@ -1,6 +1,6 @@
+
+
diff --git a/app/src/main/res/drawable/notes.xml b/app/src/main/res/drawable/icon_notes.xml
similarity index 100%
rename from app/src/main/res/drawable/notes.xml
rename to app/src/main/res/drawable/icon_notes.xml
diff --git a/app/src/main/res/drawable/icon_star_filled.xml b/app/src/main/res/drawable/icon_star_filled.xml
new file mode 100644
index 00000000..1205fcaf
--- /dev/null
+++ b/app/src/main/res/drawable/icon_star_filled.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/icon_star_outline.xml b/app/src/main/res/drawable/icon_star_outline.xml
new file mode 100644
index 00000000..2ec3545f
--- /dev/null
+++ b/app/src/main/res/drawable/icon_star_outline.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/basil/src/main/java/com/jocmp/basil/Account.kt b/basil/src/main/java/com/jocmp/basil/Account.kt
index 465c2d5e..8d7b9874 100644
--- a/basil/src/main/java/com/jocmp/basil/Account.kt
+++ b/basil/src/main/java/com/jocmp/basil/Account.kt
@@ -142,13 +142,16 @@ data class Account(
return folders.find { it.title == title }
}
- fun findArticle(articleID: Long?): Article? {
- articleID ?: return null
+ fun findArticle(articleID: String): Article? {
+ return articles.fetch(articleID = articleID)
+ }
+
+ fun addStar(articleID: String) {
+ articles.addStar(articleID = articleID)
+ }
- return database.articlesQueries.findBy(
- articleID = articleID,
- mapper = ::articleMapper
- ).executeAsOneOrNull()
+ fun removeStar(articleID: String) {
+ articles.removeStar(articleID = articleID)
}
fun markRead(articleID: String) {
@@ -156,7 +159,7 @@ data class Account(
}
fun markUnread(articleID: String) {
- articles.markUnread(articleID)
+ articles.markUnread(articleID = articleID)
}
private fun updateArticles(feed: Feed, items: List) {
diff --git a/basil/src/main/java/com/jocmp/basil/persistence/ArticleRecords.kt b/basil/src/main/java/com/jocmp/basil/persistence/ArticleRecords.kt
index 41d76e5f..230b923f 100644
--- a/basil/src/main/java/com/jocmp/basil/persistence/ArticleRecords.kt
+++ b/basil/src/main/java/com/jocmp/basil/persistence/ArticleRecords.kt
@@ -12,6 +12,17 @@ class ArticleRecords internal constructor(
val byStatus = ByStatus(database)
val byFeed = ByFeed(database)
+ fun fetch(articleID: String): Article? {
+ val id = articleID.toLongOrNull()
+
+ id ?: return null
+
+ return database.articlesQueries.findBy(
+ articleID = id,
+ mapper = ::articleMapper
+ ).executeAsOneOrNull()
+ }
+
fun markRead(articleID: String, lastReadAt: ZonedDateTime = ZonedDateTime.now()) {
database.articlesQueries.markRead(
articleID = articleID.toLong(),
@@ -28,6 +39,20 @@ class ArticleRecords internal constructor(
)
}
+ fun addStar(articleID: String) {
+ database.articlesQueries.markStarred(
+ articleID = articleID.toLong(),
+ starred = true
+ )
+ }
+
+ fun removeStar(articleID: String) {
+ database.articlesQueries.markStarred(
+ articleID = articleID.toLong(),
+ starred = false
+ )
+ }
+
class ByFeed(private val database: Database) {
fun all(
feedIDs: List,
diff --git a/basil/src/main/sqldelight/com/jocmp/basil/db/articles.sq b/basil/src/main/sqldelight/com/jocmp/basil/db/articles.sq
index ef14e2c8..568af063 100644
--- a/basil/src/main/sqldelight/com/jocmp/basil/db/articles.sq
+++ b/basil/src/main/sqldelight/com/jocmp/basil/db/articles.sq
@@ -105,3 +105,13 @@ WHERE EXISTS (
AND articles.external_id = article_statuses.external_id AND article_statuses.feed_id = articles.feed_id
LIMIT 1
);
+
+markStarred:
+UPDATE article_statuses SET starred = :starred
+WHERE EXISTS (
+ SELECT id
+ FROM articles
+ WHERE articles.id = :articleID
+ AND articles.external_id = article_statuses.external_id AND article_statuses.feed_id = articles.feed_id
+ LIMIT 1
+);
diff --git a/basil/src/test/java/com/jocmp/basil/persistence/ArticleRecordsTest.kt b/basil/src/test/java/com/jocmp/basil/persistence/ArticleRecordsTest.kt
index f79dc591..a3c9fd95 100644
--- a/basil/src/test/java/com/jocmp/basil/persistence/ArticleRecordsTest.kt
+++ b/basil/src/test/java/com/jocmp/basil/persistence/ArticleRecordsTest.kt
@@ -7,8 +7,8 @@ import com.jocmp.basil.fixtures.ArticleFixture
import com.jocmp.basil.repeated
import org.junit.Before
import org.junit.Test
-import kotlin.math.exp
import kotlin.test.assertEquals
+import kotlin.test.assertFalse
import kotlin.test.assertTrue
class ArticleRecordsTest {
@@ -75,4 +75,40 @@ class ArticleRecordsTest {
assertEquals(expected = 2, actual = count)
assertEquals(actual = actual, expected = expected)
}
+
+ @Test
+ fun markUnread() {
+ val article = articleFixture.create()
+ val articleRecords = ArticleRecords(database)
+
+ articleRecords.markUnread(articleID = article.id)
+
+ val reloaded = articleRecords.fetch(articleID = article.id)!!
+
+ assertFalse(reloaded.read)
+ }
+
+ @Test
+ fun addStar() {
+ val article = articleFixture.create()
+ val articleRecords = ArticleRecords(database)
+
+ articleRecords.addStar(articleID = article.id)
+
+ val reloaded = articleRecords.fetch(articleID = article.id)!!
+
+ assertTrue(reloaded.starred)
+ }
+
+ @Test
+ fun removeStar() {
+ val article = articleFixture.create()
+ val articleRecords = ArticleRecords(database)
+
+ articleRecords.removeStar(articleID = article.id)
+
+ val reloaded = articleRecords.fetch(articleID = article.id)!!
+
+ assertFalse(reloaded.starred)
+ }
}