Skip to content

Commit 5220ea4

Browse files
authored
Podcast tweaks from feedback (#1695)
* Add audio indicator in feed list * Stop audio when playback ends
1 parent f4fb1ab commit 5220ea4

15 files changed

Lines changed: 110 additions & 46 deletions

File tree

app/src/main/java/com/capyreader/app/ui/articles/ArticleRow.kt

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import androidx.compose.foundation.layout.size
1616
import androidx.compose.foundation.layout.width
1717
import androidx.compose.foundation.shape.RoundedCornerShape
1818
import androidx.compose.material.icons.Icons
19+
import androidx.compose.material.icons.rounded.PlayArrow
1920
import androidx.compose.material.icons.rounded.Star
2021
import androidx.compose.material3.Icon
2122
import androidx.compose.material3.ListItem
@@ -59,6 +60,7 @@ import com.capyreader.app.ui.fixtures.PreviewKoinApplication
5960
import com.capyreader.app.ui.theme.CapyTheme
6061
import com.capyreader.app.ui.theme.LocalAppTheme
6162
import com.jocmp.capy.Article
63+
import com.jocmp.capy.EnclosureType
6264
import com.jocmp.capy.MarkRead
6365
import com.jocmp.capy.articles.relativeTime
6466
import java.net.URL
@@ -126,35 +128,50 @@ fun ArticleRow(
126128
.fillMaxWidth()
127129
.padding(bottom = 2.dp)
128130
) {
131+
129132
if (options.showFeedName) {
130133
Text(
131134
text = article.feedName,
132135
color = feedNameColor,
133136
maxLines = 1,
134137
overflow = TextOverflow.Ellipsis,
135-
modifier = Modifier.weight(1f),
136138
fontWeight = if (deEmphasizeFontWeight) FontWeight.Light else null,
139+
modifier = Modifier.weight(1f)
137140
)
138141
Spacer(Modifier.width(16.dp))
139142
}
140-
if (article.starred) {
141-
Icon(
142-
Icons.Rounded.Star,
143-
contentDescription = null,
144-
tint = feedNameColor,
145-
modifier = Modifier
146-
.width(12.dp.relative(options.fontScale))
147-
.padding(end = 2.dp)
143+
Row(
144+
horizontalArrangement = Arrangement.spacedBy(2.dp),
145+
verticalAlignment = Alignment.CenterVertically,
146+
) {
147+
if (article.starred) {
148+
Icon(
149+
Icons.Rounded.Star,
150+
contentDescription = null,
151+
tint = feedNameColor,
152+
modifier = Modifier
153+
.width(12.dp.relative(options.fontScale))
154+
)
155+
}
156+
if (article.enclosureType == EnclosureType.AUDIO) {
157+
Icon(
158+
Icons.Rounded.PlayArrow,
159+
contentDescription = null,
160+
tint = feedNameColor,
161+
modifier = Modifier
162+
.width(16.dp.relative(options.fontScale))
163+
.padding(end = 2.dp)
164+
)
165+
}
166+
Text(
167+
text = relativeTime(
168+
time = article.publishedAt,
169+
currentTime = currentTime,
170+
),
171+
color = feedNameColor,
172+
maxLines = 1,
148173
)
149174
}
150-
Text(
151-
text = relativeTime(
152-
time = article.publishedAt,
153-
currentTime = currentTime,
154-
),
155-
color = feedNameColor,
156-
maxLines = 1,
157-
)
158175
}
159176
},
160177
supportingContent = {
@@ -168,7 +185,7 @@ fun ArticleRow(
168185
maxLines = 2,
169186
overflow = TextOverflow.Ellipsis,
170187
fontWeight = if (deEmphasizeFontWeight) FontWeight.Light else null,
171-
)
188+
)
172189
}
173190
if (imageURL != null && options.imagePreview == ImagePreview.LARGE) {
174191
ArticleImage(imageURL = imageURL, imagePreview = options.imagePreview)
@@ -365,6 +382,7 @@ fun ArticleRowPreview_Selected_DarkMode() {
365382
publishedAt = ZonedDateTime.of(2024, 2, 11, 8, 33, 0, 0, ZoneOffset.UTC),
366383
read = true,
367384
starred = false,
385+
enclosureType = EnclosureType.AUDIO,
368386
feedName = "9to5Google - Google news, Pixel, Android, Home, Chrome OS, more"
369387
)
370388

@@ -432,7 +450,11 @@ fun ArticleRowPreview_Medium(@PreviewParameter(ArticleSample::class) article: Ar
432450
PreviewKoinApplication {
433451
CapyTheme {
434452
ArticleRow(
435-
article = article.copy(imageURL = "http://example.com"),
453+
article = article.copy(
454+
imageURL = "http://example.com",
455+
starred = true,
456+
enclosureType = EnclosureType.AUDIO
457+
),
436458
index = 0,
437459
selected = true,
438460
onSelect = {},

app/src/main/java/com/capyreader/app/ui/articles/audio/AudioPlayerController.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,7 @@ class AudioPlayerController(
9292
}
9393
if (playbackState == Player.STATE_ENDED) {
9494
_isPlaying.value = false
95-
_currentPosition.value = 0L
96-
controller.seekTo(0)
95+
controller.pause()
9796
}
9897
}
9998
})

capy/src/main/java/com/jocmp/capy/Article.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ data class Article(
2525
val fullContent: FullContentState = FullContentState.NONE,
2626
val content: String = contentHTML.ifBlank { summary },
2727
val enclosures: List<Enclosure> = emptyList(),
28+
val enclosureType: EnclosureType? = null,
2829
) {
2930
val defaultContent = contentHTML.ifBlank { summary }
3031

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.jocmp.capy
2+
3+
enum class EnclosureType {
4+
AUDIO,
5+
VIDEO;
6+
7+
companion object {
8+
fun from(mimeType: String?): EnclosureType? {
9+
if (mimeType == null) {
10+
return null
11+
}
12+
13+
return when {
14+
mimeType.startsWith("audio/") -> AUDIO
15+
mimeType.startsWith("video/") -> VIDEO
16+
else -> null
17+
}
18+
}
19+
}
20+
}

capy/src/main/java/com/jocmp/capy/accounts/feedbin/FeedbinAccountDelegate.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,8 @@ internal class FeedbinAccountDelegate(
379379
entries.forEach { entry ->
380380
val updated = TimeHelpers.nowUTC()
381381
val articleID = entry.id.toString()
382+
val enclosure = entry.enclosure
383+
val enclosureType = enclosure?.enclosure_type
382384

383385
database.articlesQueries.create(
384386
id = articleID,
@@ -391,6 +393,7 @@ internal class FeedbinAccountDelegate(
391393
summary = entry.summary,
392394
image_url = entry.images?.size_1?.cdn_url,
393395
published_at = entry.published.toDateTime?.toEpochSecond(),
396+
enclosure_type = enclosureType,
394397
)
395398

396399
articleRecords.createStatus(
@@ -399,9 +402,6 @@ internal class FeedbinAccountDelegate(
399402
read = true
400403
)
401404

402-
val enclosure = entry.enclosure
403-
val enclosureType = enclosure?.enclosure_type
404-
405405
if (enclosure != null && enclosureType != null) {
406406
enclosureRecords.create(
407407
url = enclosure.enclosure_url,

capy/src/main/java/com/jocmp/capy/accounts/local/LocalAccountDelegate.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,8 @@ internal class LocalAccountDelegate(
241241
val blocked = containsBlockedText(parsedItem, blocklist)
242242

243243
if (parsedItem.id != null && withinCutoff && !blocked) {
244+
val enclosureType = parsedItem.enclosures.firstOrNull()?.type
245+
244246
database.articlesQueries.create(
245247
id = parsedItem.id,
246248
feed_id = feed.id,
@@ -252,6 +254,7 @@ internal class LocalAccountDelegate(
252254
extracted_content_url = null,
253255
image_url = parsedItem.imageURL,
254256
published_at = publishedAt,
257+
enclosure_type = enclosureType,
255258
)
256259

257260
articleRecords.createStatus(

capy/src/main/java/com/jocmp/capy/accounts/reader/ReaderAccountDelegate.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,8 @@ internal class ReaderAccountDelegate(
450450

451451
items.forEach { item ->
452452
val updated = TimeHelpers.nowUTC()
453+
val enclosures = ReaderEnclosureParsing.validEnclosures(item)
454+
val enclosureType = enclosures.firstOrNull()?.type
453455

454456
database.articlesQueries.create(
455457
id = item.hexID,
@@ -461,7 +463,8 @@ internal class ReaderAccountDelegate(
461463
summary = item.summary.content?.let { Jsoup.parse(it).text() },
462464
url = item.canonical.firstOrNull()?.href,
463465
image_url = ReaderEnclosureParsing.parsedImageURL(item),
464-
published_at = item.published
466+
published_at = item.published,
467+
enclosure_type = enclosureType,
465468
)
466469

467470
articleRecords.updateStatus(
@@ -487,7 +490,7 @@ internal class ReaderAccountDelegate(
487490
)
488491
}
489492

490-
ReaderEnclosureParsing.validEnclosures(item).forEach {
493+
enclosures.forEach {
491494
enclosureRecords.create(
492495
url = it.url.toString(),
493496
type = it.type,

capy/src/main/java/com/jocmp/capy/persistence/ArticleMapper.kt

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.jocmp.capy.persistence
22

33
import com.jocmp.capy.Article
4+
import com.jocmp.capy.EnclosureType
45
import com.jocmp.capy.common.optionalURL
56
import com.jocmp.capy.common.toDateTimeFromSeconds
67

@@ -10,11 +11,12 @@ internal fun articleMapper(
1011
title: String?,
1112
author: String?,
1213
contentHtml: String?,
13-
extractedContentURL: String? = null,
14+
extractedContentURL: String?,
1415
url: String?,
1516
summary: String?,
1617
imageURL: String?,
1718
publishedAt: Long?,
19+
enclosureType: String?,
1820
feedTitle: String?,
1921
faviconURL: String?,
2022
enableStickyContent: Boolean,
@@ -44,6 +46,7 @@ internal fun articleMapper(
4446
feedName = feedTitle ?: "",
4547
enableStickyFullContent = enableStickyContent,
4648
openInBrowser = openInBrowser,
49+
enclosureType = EnclosureType.from(enclosureType),
4750
)
4851
}
4952

@@ -56,6 +59,7 @@ internal fun listMapper(
5659
summary: String?,
5760
imageURL: String?,
5861
publishedAt: Long?,
62+
enclosureType: String?,
5963
feedTitle: String?,
6064
faviconURL: String?,
6165
openInBrowser: Boolean,
@@ -66,27 +70,29 @@ internal fun listMapper(
6670
return articleMapper(
6771
id = id,
6872
feedID = feedID.toString(),
69-
faviconURL = faviconURL,
7073
title = title ?: "",
7174
author = author,
7275
contentHtml = "",
73-
enableStickyContent = false,
74-
feedURL = null,
75-
siteURL = null,
76+
extractedContentURL = null,
7677
url = url,
77-
feedTitle = feedTitle,
78-
imageURL = imageURL,
7978
summary = if (!summary.isNullOrBlank()) {
8079
summary
8180
} else if (title.isNullOrBlank()) {
8281
url.orEmpty()
8382
} else {
8483
""
8584
},
85+
imageURL = imageURL,
86+
publishedAt = publishedAt,
87+
enclosureType = enclosureType,
88+
feedTitle = feedTitle,
89+
faviconURL = faviconURL,
90+
enableStickyContent = false,
91+
openInBrowser = openInBrowser,
92+
feedURL = null,
93+
siteURL = null,
8694
updatedAt = updatedAt,
87-
read = read ?: false,
8895
starred = starred ?: false,
89-
publishedAt = publishedAt,
90-
openInBrowser = openInBrowser ?: false,
96+
read = read ?: false,
9197
)
9298
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ALTER TABLE articles ADD COLUMN enclosure_type TEXT;

capy/src/main/sqldelight/com/jocmp/capy/db/articles.sq

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ INSERT INTO articles(
5353
url,
5454
summary,
5555
image_url,
56-
published_at
56+
published_at,
57+
enclosure_type
5758
)
5859
VALUES (
5960
:id,
@@ -65,7 +66,8 @@ VALUES (
6566
:url,
6667
:summary,
6768
:image_url,
68-
:published_at
69+
:published_at,
70+
:enclosure_type
6971
)
7072
ON CONFLICT(id) DO UPDATE
7173
SET
@@ -78,7 +80,8 @@ extracted_content_url = excluded.extracted_content_url,
7880
url = excluded.url,
7981
summary = excluded.summary,
8082
image_url = excluded.image_url,
81-
published_at = published_at;
83+
published_at = published_at,
84+
enclosure_type = excluded.enclosure_type;
8285

8386
createStatus:
8487
INSERT INTO article_statuses(

0 commit comments

Comments
 (0)