mirror of https://github.com/readrops/Readrops.git
Display feed image in FeedBottomSheet when available
This commit is contained in:
parent
cbd5c1bc3d
commit
db04cdddb7
|
@ -28,6 +28,7 @@ class ATOMFeedAdapter : XmlAdapter<Pair<Feed, List<Item>>> {
|
|||
"title" -> name = nonNullText()
|
||||
"link" -> parseLink(this@allChildrenAutoIgnore, feed)
|
||||
"subtitle" -> description = nullableText()
|
||||
"logo" -> imageUrl = nullableText()
|
||||
"entry" -> items += itemAdapter.fromXml(this@allChildrenAutoIgnore)
|
||||
else -> skipContents()
|
||||
}
|
||||
|
@ -52,6 +53,6 @@ class ATOMFeedAdapter : XmlAdapter<Pair<Feed, List<Item>>> {
|
|||
}
|
||||
|
||||
companion object {
|
||||
val names = Names.of("title", "link", "subtitle", "entry")
|
||||
val names = Names.of("title", "link", "subtitle", "logo", "entry")
|
||||
}
|
||||
}
|
|
@ -5,7 +5,9 @@ import com.readrops.api.utils.extensions.nextNonEmptyString
|
|||
import com.readrops.api.utils.extensions.nextNullableString
|
||||
import com.readrops.db.entities.Feed
|
||||
import com.readrops.db.entities.Item
|
||||
import com.squareup.moshi.*
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.JsonReader
|
||||
import com.squareup.moshi.JsonWriter
|
||||
|
||||
class JSONFeedAdapter : JsonAdapter<Pair<Feed, List<Item>>>() {
|
||||
|
||||
|
@ -27,8 +29,9 @@ class JSONFeedAdapter : JsonAdapter<Pair<Feed, List<Item>>>() {
|
|||
0 -> name = reader.nextNonEmptyString()
|
||||
1 -> siteUrl = reader.nextNullableString()
|
||||
2 -> url = reader.nextNullableString()
|
||||
3 -> description = reader.nextNullableString()
|
||||
4 -> items += itemAdapter.fromJson(reader)
|
||||
3 -> imageUrl = reader.nextNullableString()
|
||||
4 -> description = reader.nextNullableString()
|
||||
5 -> items += itemAdapter.fromJson(reader)
|
||||
else -> reader.skipValue()
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +45,6 @@ class JSONFeedAdapter : JsonAdapter<Pair<Feed, List<Item>>>() {
|
|||
|
||||
companion object {
|
||||
val names: JsonReader.Options = JsonReader.Options.of("title", "home_page_url",
|
||||
"feed_url", "description", "items")
|
||||
"feed_url", "icon", "description", "items")
|
||||
}
|
||||
}
|
|
@ -40,8 +40,10 @@ class RSS1FeedAdapter : XmlAdapter<Pair<Feed, List<Item>>> {
|
|||
}
|
||||
|
||||
private fun parseChannel(konsumer: Konsumer, feed: Feed) = with(konsumer) {
|
||||
feed.url = attributes.getValueOrNull("about",
|
||||
namespace = "http://www.w3.org/1999/02/22-rdf-syntax-ns#")
|
||||
feed.url = attributes.getValueOrNull(
|
||||
localName = "about",
|
||||
namespace = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
)
|
||||
|
||||
allChildrenAutoIgnore(names) {
|
||||
with(feed) {
|
||||
|
@ -49,12 +51,16 @@ class RSS1FeedAdapter : XmlAdapter<Pair<Feed, List<Item>>> {
|
|||
"title" -> name = nonNullText()
|
||||
"link" -> siteUrl = nonNullText()
|
||||
"description" -> description = nullableText()
|
||||
"image" -> imageUrl = attributes.getValueOrNull(
|
||||
localName = "resource",
|
||||
namespace = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val names = Names.of("title", "link", "description")
|
||||
val names = Names.of("title", "link", "description", "image")
|
||||
}
|
||||
}
|
|
@ -35,6 +35,7 @@ class RSS2FeedAdapter : XmlAdapter<Pair<Feed, List<Item>>> {
|
|||
url = attributes.getValueOrNull("href")
|
||||
}
|
||||
"item" -> items += itemAdapter.fromXml(this@allChildrenAutoIgnore)
|
||||
"image" -> imageUrl = parseImage(this@allChildrenAutoIgnore)
|
||||
else -> skipContents()
|
||||
}
|
||||
}
|
||||
|
@ -49,7 +50,20 @@ class RSS2FeedAdapter : XmlAdapter<Pair<Feed, List<Item>>> {
|
|||
}
|
||||
}
|
||||
|
||||
private fun parseImage(konsumer: Konsumer): String? = with(konsumer) {
|
||||
var url: String? = null
|
||||
|
||||
allChildrenAutoIgnore(Names.of("url")) {
|
||||
when (tagName) {
|
||||
"url" -> url = nullableText()
|
||||
else -> skipContents()
|
||||
}
|
||||
}
|
||||
|
||||
url
|
||||
}
|
||||
|
||||
companion object {
|
||||
val names = Names.of("title", "description", "link", "item")
|
||||
val names = Names.of("title", "description", "link", "item", "image")
|
||||
}
|
||||
}
|
|
@ -27,6 +27,7 @@ class ATOMAdapterTest {
|
|||
assertEquals(url, "https://github.com/readrops/Readrops/commits/develop.atom")
|
||||
assertEquals(siteUrl, "https://github.com/readrops/Readrops/commits/develop")
|
||||
assertEquals(description, "Here is a subtitle")
|
||||
assertEquals(imageUrl, "https://github.com/readrops/Readrops/blob/develop/images/readrops_logo.png")
|
||||
}
|
||||
|
||||
with(items[0]) {
|
||||
|
|
|
@ -35,6 +35,7 @@ class JSONFeedAdapterTest {
|
|||
assertEquals(url, "http://flyingmeat.com/blog/feed.json")
|
||||
assertEquals(siteUrl, "http://flyingmeat.com/blog/")
|
||||
assertEquals(description, "News from your friends at Flying Meat.")
|
||||
assertEquals(imageUrl, "https://secure.flyingmeat.com/favicon.ico")
|
||||
}
|
||||
|
||||
with(items[0]) {
|
||||
|
|
|
@ -28,6 +28,7 @@ class RSS1AdapterTest {
|
|||
assertEquals(url, "https://slashdot.org/")
|
||||
assertEquals(siteUrl, "https://slashdot.org/")
|
||||
assertEquals(description, "News for nerds, stuff that matters")
|
||||
assertEquals(imageUrl, "https://a.fsdn.com/sd/topics/topicslashdot.gif")
|
||||
}
|
||||
|
||||
with(items[0]) {
|
||||
|
|
|
@ -27,6 +27,7 @@ class RSS2AdapterTest {
|
|||
assertEquals(url, "https://news.ycombinator.com/feed/")
|
||||
assertEquals(siteUrl, "https://news.ycombinator.com/")
|
||||
assertEquals(description, "Links for the intellectually curious, ranked by readers.")
|
||||
assertEquals(imageUrl, "https://news.ycombinator.com/y18.svg")
|
||||
}
|
||||
|
||||
with(items[0]) {
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<title>Recent Commits to Readrops:develop</title>
|
||||
<updated>2020-09-06T21:09:59Z</updated>
|
||||
<subtitle>Here is a subtitle</subtitle>
|
||||
<logo>https://github.com/readrops/Readrops/blob/develop/images/readrops_logo.png</logo>
|
||||
<entry>
|
||||
<id>tag:github.com,2008:Grit::Commit/c15f093a1bc4211e85f8d1817c9073e307afe5ac</id>
|
||||
<link type="text/html" rel="alternate" href="https://github.com/readrops/Readrops/commit/c15f093a1bc4211e85f8d1817c9073e307afe5ac"/>
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
"home_page_url": "http://flyingmeat.com/blog/",
|
||||
"feed_url": "http://flyingmeat.com/blog/feed.json",
|
||||
"description": "News from your friends at Flying Meat.",
|
||||
"icon": "https://secure.flyingmeat.com/favicon.ico",
|
||||
"author": {
|
||||
"name": "Gus Mueller"
|
||||
},
|
||||
|
|
|
@ -6,6 +6,11 @@
|
|||
<atom:link href="https://news.ycombinator.com/feed/" rel="self" />
|
||||
<link>https://news.ycombinator.com/</link>
|
||||
<description>Links for the intellectually curious, ranked by readers.</description>
|
||||
<image>
|
||||
<title>Hacker News</title>
|
||||
<url>https://news.ycombinator.com/y18.svg</url>
|
||||
<link>https://news.ycombinator.com/</link>
|
||||
</image>
|
||||
<item>
|
||||
<title>Africa declared free of wild polio</title>
|
||||
<link>https://www.bbc.com/news/world-africa-53887947</link>
|
||||
|
|
|
@ -20,11 +20,16 @@ import androidx.compose.material3.Text
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.blur
|
||||
import androidx.compose.ui.draw.drawWithContent
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import coil3.compose.AsyncImage
|
||||
import com.readrops.app.R
|
||||
import com.readrops.app.util.theme.LargeSpacer
|
||||
|
@ -46,53 +51,87 @@ fun FeedModalBottomSheet(
|
|||
canDeleteFeed: Boolean
|
||||
) {
|
||||
ModalBottomSheet(
|
||||
dragHandle = null,
|
||||
onDismissRequest = { onDismissRequest() }
|
||||
) {
|
||||
Column {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(
|
||||
horizontal = MaterialTheme.spacing.largeSpacing
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
AsyncImage(
|
||||
model = feed.iconUrl,
|
||||
contentDescription = feed.name!!,
|
||||
placeholder = painterResource(id = R.drawable.ic_rss_feed_grey),
|
||||
error = painterResource(id = R.drawable.ic_rss_feed_grey),
|
||||
modifier = Modifier.size(MaterialTheme.spacing.veryLargeSpacing)
|
||||
)
|
||||
if (feed.imageUrl != null) {
|
||||
AsyncImage(
|
||||
model = feed.imageUrl,
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier
|
||||
.matchParentSize()
|
||||
.drawWithContent {
|
||||
drawContent()
|
||||
drawRect(
|
||||
color = Color.Black.copy(alpha = 0.65f)
|
||||
)
|
||||
}
|
||||
.blur(2.5.dp)
|
||||
)
|
||||
}
|
||||
|
||||
MediumSpacer()
|
||||
|
||||
Column {
|
||||
Text(
|
||||
text = feed.name!!,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(
|
||||
top = MaterialTheme.spacing.largeSpacing,
|
||||
start = MaterialTheme.spacing.largeSpacing,
|
||||
end = MaterialTheme.spacing.largeSpacing,
|
||||
bottom = MaterialTheme.spacing.mediumSpacing
|
||||
)
|
||||
) {
|
||||
AsyncImage(
|
||||
model = feed.iconUrl,
|
||||
contentDescription = feed.name!!,
|
||||
placeholder = painterResource(id = R.drawable.ic_rss_feed_grey),
|
||||
error = painterResource(id = R.drawable.ic_rss_feed_grey),
|
||||
modifier = Modifier.size(MaterialTheme.spacing.veryLargeSpacing)
|
||||
)
|
||||
|
||||
if (feed.description != null) {
|
||||
VeryShortSpacer()
|
||||
MediumSpacer()
|
||||
|
||||
Column {
|
||||
Text(
|
||||
text = feed.description!!,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
maxLines = 2,
|
||||
text = feed.name!!,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = if (feed.imageUrl != null) {
|
||||
Color.White
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onBackground
|
||||
},
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
|
||||
if (feed.description != null) {
|
||||
VeryShortSpacer()
|
||||
|
||||
Text(
|
||||
text = feed.description!!,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = if (feed.imageUrl != null) {
|
||||
Color.White
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onSurfaceVariant
|
||||
},
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MediumSpacer()
|
||||
|
||||
HorizontalDivider(
|
||||
modifier = Modifier.padding(horizontal = MaterialTheme.spacing.mediumSpacing)
|
||||
)
|
||||
if (feed.imageUrl == null) {
|
||||
HorizontalDivider(
|
||||
modifier = Modifier.padding(horizontal = MaterialTheme.spacing.mediumSpacing)
|
||||
)
|
||||
}
|
||||
|
||||
MediumSpacer()
|
||||
|
||||
|
|
|
@ -44,6 +44,7 @@ class GetFoldersWithFeeds(
|
|||
id = it.feedId,
|
||||
name = it.feedName,
|
||||
iconUrl = it.feedIcon,
|
||||
imageUrl = it.feedImage,
|
||||
url = it.feedUrl,
|
||||
siteUrl = it.feedSiteUrl,
|
||||
description = it.feedDescription,
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 5,
|
||||
"identityHash": "0bac941f8b1b6003c35a6d0cdc1f2e13",
|
||||
"identityHash": "63de09bfed367e5705a0889d928f056d",
|
||||
"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 )",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `description` TEXT, `url` TEXT, `image_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",
|
||||
|
@ -32,6 +32,12 @@
|
|||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "imageUrl",
|
||||
"columnName": "image_url",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "siteUrl",
|
||||
"columnName": "siteUrl",
|
||||
|
@ -520,7 +526,7 @@
|
|||
"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')"
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '63de09bfed367e5705a0889d928f056d')"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -121,5 +121,8 @@ object MigrationFrom4To5 : Migration(4, 5) {
|
|||
|
||||
db.execSQL("DROP TABLE IF EXISTS `Account`")
|
||||
db.execSQL("ALTER TABLE `_new_Account` RENAME TO `Account`")
|
||||
|
||||
// add image_url field
|
||||
db.execSQL("""ALTER TABLE `Feed` ADD `image_url` TEXT DEFAULT NULL""")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ data class Feed(
|
|||
var name: String? = null,
|
||||
var description: String? = null,
|
||||
var url: String? = null,
|
||||
@ColumnInfo("image_url") var imageUrl: String? = null,
|
||||
var siteUrl: String? = null,
|
||||
@ColumnInfo("last_updated") var lastUpdated: String? = null,
|
||||
@ColorInt var color: Int = 0,
|
||||
|
|
|
@ -16,6 +16,7 @@ data class FolderWithFeed(
|
|||
val feedId: Int = 0,
|
||||
val feedName: String? = null,
|
||||
val feedIcon: String? = null,
|
||||
val feedImage: String? = null,
|
||||
val feedUrl: String? = null,
|
||||
val feedDescription: String? = null,
|
||||
val feedSiteUrl: String? = null,
|
||||
|
|
|
@ -11,6 +11,7 @@ object FoldersAndFeedsQueryBuilder {
|
|||
"Feed.name As feedName",
|
||||
"Feed.icon_url As feedIcon",
|
||||
"Feed.url As feedUrl",
|
||||
"Feed.image_url as feedImage",
|
||||
"Feed.siteUrl As feedSiteUrl",
|
||||
"Feed.description as feedDescription",
|
||||
"Feed.remote_id as feedRemoteId",
|
||||
|
|
|
@ -49,6 +49,7 @@ lifecyle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-comp
|
|||
|
||||
coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" }
|
||||
coil-http = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil" }
|
||||
coil-svg = { module = "io.coil-kt.coil3:coil-svg", version.ref = "coil" }
|
||||
|
||||
coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
|
||||
coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
|
||||
|
@ -112,7 +113,7 @@ compose = ["compose-foundation", "compose-runtime", "compose-animation",
|
|||
voyager = ["voyager-navigator", "voyager-screenmodel", "voyager-tab-navigator", "voyager-koin", "voyager-transitions"]
|
||||
lifecycle = ["lifecycle-viewmodel-ktx", "lifecycle-viewmodel-compose", "lifecycle-viewmodel-savedstate",
|
||||
"lifecyle-runtime-compose"]
|
||||
coil = ["coil-compose", "coil-http"]
|
||||
coil = ["coil-compose", "coil-http", "coil-svg"]
|
||||
coroutines = ["coroutines-core", "coroutines-android"]
|
||||
room = ["room-runtime", "room-ktx", "room-paging"]
|
||||
koin = ["koin-core", "koin-android", "koin-androidx-compose"]
|
||||
|
|
Loading…
Reference in New Issue