Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,9 @@ class AppPreferences(context: Context) {
val confirmMarkAllRead: Preference<Boolean>
get() = preferenceStore.getBoolean("article_list_confirm_mark_all_read", true)

val showReadingTime: Preference<Boolean>
get() = preferenceStore.getBoolean("article_list_show_reading_time", false)

val markReadOnScroll: Preference<Boolean>
get() = preferenceStore.getBoolean("article_list_mark_read_on_scroll", false)

Expand Down
41 changes: 41 additions & 0 deletions app/src/main/java/com/capyreader/app/sync/ReadingTimeWorker.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.capyreader.app.sync

import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.jocmp.capy.Account
import com.jocmp.capy.articles.ReadingTime
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject

class ReadingTimeWorker(
appContext: Context,
workerParams: WorkerParameters
) : CoroutineWorker(appContext, workerParams), KoinComponent {
private val account by inject<Account>()

override suspend fun doWork(): Result {
val batchSize = 500L
var processed: Int

do {
val articles = account.database.articlesQueries
.articlesWithMissingReadingTime(batchSize)
.executeAsList()
processed = articles.size

articles.forEach { row ->
val minutes = ReadingTime.calculate(row.content_html)
if (minutes != null) {
account.database.articlesQueries
.updateReadingTime(
readingTimeMinutes = minutes,
articleID = row.id
)
}
}
} while (processed >= batchSize.toInt())

return Result.success()
}
}
1 change: 1 addition & 0 deletions app/src/main/java/com/capyreader/app/sync/SyncModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ import org.koin.dsl.module
val syncModule = module {
worker { ReadSyncWorker(get(), get()) }
worker { StarSyncWorker(get(), get()) }
worker { ReadingTimeWorker(get(), get()) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -187,11 +187,14 @@ fun rememberArticleOptions(appPreferences: AppPreferences = koinInject()): Artic
val fontScale by appPreferences.articleListOptions.fontScale.stateIn(scope).collectAsState()
val shortenTitles by appPreferences.articleListOptions.shortenTitles.stateIn(scope)
.collectAsState()
val showReadingTime by appPreferences.articleListOptions.showReadingTime.stateIn(scope)
.collectAsState()

return ArticleRowOptions(
showSummary = showSummary,
showIcon = showIcon,
showFeedName = showFeedName,
showReadingTime = showReadingTime,
imagePreview = imagePreview,
fontScale = fontScale,
shortenTitles = shortenTitles,
Expand Down
18 changes: 18 additions & 0 deletions app/src/main/java/com/capyreader/app/ui/articles/ArticleRow.kt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextDirection
Expand Down Expand Up @@ -72,6 +73,7 @@ data class ArticleRowOptions(
val showIcon: Boolean = true,
val showSummary: Boolean = true,
val showFeedName: Boolean = true,
val showReadingTime: Boolean = false,
val imagePreview: ImagePreview = ImagePreview.default,
val fontScale: ArticleListFontScale = ArticleListFontScale.MEDIUM,
val shortenTitles: Boolean = true,
Expand Down Expand Up @@ -165,6 +167,22 @@ fun ArticleRow(
.padding(end = 2.dp)
)
}
val readingTimeMinutes = article.readingTimeMinutes
if (options.showReadingTime && readingTimeMinutes != null) {
Text(
text = pluralStringResource(
R.plurals.reading_time_minutes,
readingTimeMinutes.toInt(),
readingTimeMinutes.toInt()
),
color = feedNameColor,
maxLines = 1,
)
Text(
text = "·",
color = feedNameColor,
)
}
Text(
text = relativeTime(
time = article.publishedAt,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,25 @@ import com.jocmp.capy.common.toDeviceDateTime
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle

fun Article.byline(context: Context): String {
fun Article.byline(context: Context, showReadingTime: Boolean = false): String {
val deviceDateTime = publishedAt.toDeviceDateTime()
val date = DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG).format(deviceDateTime)
val time = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).format(deviceDateTime)
val minutes = readingTimeMinutes

if (showReadingTime && minutes != null) {
val readingTimeText = context.resources.getQuantityString(
R.plurals.reading_time_minutes,
minutes.toInt(),
minutes.toInt()
)

return if (!author.isNullOrBlank()) {
context.getString(R.string.article_byline_with_reading_time, date, time, author, readingTimeText)
} else {
context.getString(R.string.article_byline_date_only_with_reading_time, date, time, readingTimeText)
}
}

return if (!author.isNullOrBlank()) {
context.getString(R.string.article_byline, date, time, author)
Expand Down
10 changes: 9 additions & 1 deletion app/src/main/java/com/capyreader/app/ui/components/WebView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import com.capyreader.app.common.AudioEnclosure
import com.capyreader.app.common.Media
import com.capyreader.app.common.WebViewInterface
import com.capyreader.app.common.rememberTalkbackPreference
import com.capyreader.app.preferences.AppPreferences
import com.capyreader.app.ui.articles.detail.articleTemplateColors
import com.capyreader.app.ui.articles.detail.byline
import com.jocmp.capy.Article
Expand Down Expand Up @@ -158,6 +159,7 @@ class WebViewState(
private val renderer: ArticleRenderer,
private val colors: Map<String, String>,
private val enableNativeScroll: Boolean,
private val showReadingTime: Boolean,
internal val webView: WebView,
) {
private var htmlId: String? = null
Expand All @@ -184,7 +186,10 @@ class WebViewState(
val html = renderer.render(
article,
hideImages = !showImages,
byline = article.byline(context = webView.context),
byline = article.byline(
context = webView.context,
showReadingTime = showReadingTime
),
colors = colors
)

Expand Down Expand Up @@ -239,7 +244,9 @@ fun rememberWebViewState(
isAudioPlaying: Boolean = false,
key: String? = null,
): WebViewState {
val appPreferences: AppPreferences = koinInject()
val enableNativeScroll by rememberTalkbackPreference()
val showReadingTime = appPreferences.articleListOptions.showReadingTime.get()
val colors = articleTemplateColors()
val context = LocalContext.current
val currentAudioUrlState by rememberUpdatedState(currentAudioUrl)
Expand Down Expand Up @@ -301,6 +308,7 @@ fun rememberWebViewState(
renderer,
colors,
enableNativeScroll = enableNativeScroll,
showReadingTime = showReadingTime,
webView,
).also {
client.state = it
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ val settingsModule = module {
}
viewModel {
DisplaySettingsViewModel(
context = get(),
account = get(),
appPreferences = get(),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,14 @@ data class ArticleListOptions(
val showFeedIcons: Boolean,
val showFeedName: Boolean,
val showSummary: Boolean,
val showReadingTime: Boolean,
val shortenTitles: Boolean,
val fontScale: ArticleListFontScale,
val updateFeedIcons: (show: Boolean) -> Unit,
val updateFeedName: (show: Boolean) -> Unit,
val updateImagePreview: (preview: ImagePreview) -> Unit,
val updateSummary: (show: Boolean) -> Unit,
val updateShowReadingTime: (show: Boolean) -> Unit,
val updateFontScale: (scale: ArticleListFontScale) -> Unit,
val updateShortenTitles: (show: Boolean) -> Unit,
)
Expand Down Expand Up @@ -63,6 +65,11 @@ fun ArticleListSettings(
checked = options.shortenTitles,
title = stringResource(R.string.settings_article_list_shorten_titles)
)
TextSwitch(
onCheckedChange = options.updateShowReadingTime,
checked = options.showReadingTime,
title = stringResource(R.string.settings_article_list_show_reading_time)
)
}

PreferenceSelect(
Expand Down Expand Up @@ -103,11 +110,13 @@ private fun ArticleListSettingsPreview() {
imagePreview = ImagePreview.default,
showSummary = true,
showFeedIcons = true,
showReadingTime = false,
fontScale = ArticleListFontScale.LARGE,
showFeedName = false,
shortenTitles = true,
updateImagePreview = {},
updateSummary = {},
updateShowReadingTime = {},
updateFeedName = {},
updateFeedIcons = {},
updateFontScale = {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,14 @@ fun DisplaySettingsPanel(
articleListOptions = ArticleListOptions(
imagePreview = viewModel.imagePreview,
showSummary = viewModel.showSummary,
showReadingTime = viewModel.showReadingTime,
fontScale = viewModel.fontScale,
showFeedIcons = viewModel.showFeedIcons,
showFeedName = viewModel.showFeedName,
shortenTitles = viewModel.shortenTitles,
updateImagePreview = viewModel::updateImagePreview,
updateSummary = viewModel::updateSummary,
updateShowReadingTime = viewModel::updateShowReadingTime,
updateFeedName = viewModel::updateFeedName,
updateFeedIcons = viewModel::updateFeedIcons,
updateFontScale = viewModel::updateFontScale,
Expand Down Expand Up @@ -235,12 +237,14 @@ private fun DisplaySettingsPanelViewPreview() {
articleListOptions = ArticleListOptions(
imagePreview = ImagePreview.default,
showSummary = true,
showReadingTime = false,
fontScale = ArticleListFontScale.MEDIUM,
showFeedIcons = true,
showFeedName = false,
shortenTitles = true,
updateImagePreview = {},
updateSummary = {},
updateShowReadingTime = {},
updateFeedName = {},
updateFeedIcons = {},
updateFontScale = {},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
package com.capyreader.app.ui.settings.panels

import android.content.Context
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import com.capyreader.app.common.ImagePreview
import com.capyreader.app.preferences.AppPreferences
import com.capyreader.app.preferences.LayoutPreference
import com.capyreader.app.preferences.ReaderImageVisibility
import com.capyreader.app.preferences.ThemeMode
import com.capyreader.app.preferences.AppTheme
import com.capyreader.app.sync.ReadingTimeWorker
import com.capyreader.app.ui.articles.ArticleListFontScale
import com.capyreader.app.ui.articles.MarkReadPosition
import com.jocmp.capy.Account

class DisplaySettingsViewModel(
private val context: Context,
val account: Account,
val appPreferences: AppPreferences,
) : ViewModel() {
Expand All @@ -38,6 +44,12 @@ class DisplaySettingsViewModel(

private val _shortenTitles = mutableStateOf(appPreferences.articleListOptions.shortenTitles.get())

private val _showReadingTime =
mutableStateOf(appPreferences.articleListOptions.showReadingTime.get())

val showReadingTime: Boolean
get() = _showReadingTime.value

var fontScale by mutableStateOf(appPreferences.articleListOptions.fontScale.get())
private set

Expand Down Expand Up @@ -138,4 +150,25 @@ class DisplaySettingsViewModel(

_shortenTitles.value = shortenTitles
}

fun updateShowReadingTime(show: Boolean) {
appPreferences.articleListOptions.showReadingTime.set(show)
account.preferences.showReadingTime.set(show)

_showReadingTime.value = show

if (show) {
backfillReadingTime()
}
}

private fun backfillReadingTime() {
val request = OneTimeWorkRequestBuilder<ReadingTimeWorker>().build()
WorkManager.getInstance(context)
.enqueueUniqueWork(READING_TIME_WORK, ExistingWorkPolicy.REPLACE, request)
}

companion object {
private const val READING_TIME_WORK = "reading_time_backfill"
}
}
7 changes: 7 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@
<string name="settings_article_list_feed_icons">Feed Icons</string>
<string name="settings_article_list_summary">Summary</string>
<string name="settings_article_list_shorten_titles">Shorten titles</string>
<string name="settings_article_list_show_reading_time">Reading time</string>
<string name="settings_section_version">Version</string>
<string name="settings_option_copy_version">Copy version</string>
<string name="settings_option_full_content_title">Sticky Full Content</string>
Expand Down Expand Up @@ -189,6 +190,12 @@
<string name="image_error_text">Error loading image</string>
<string name="article_byline">%1$s at %2$s by %3$s</string>
<string name="article_byline_date_only">%1$s at %2$s</string>
<string name="article_byline_with_reading_time">%1$s at %2$s by %3$s · %4$s</string>
<string name="article_byline_date_only_with_reading_time">%1$s at %2$s · %3$s</string>
<plurals name="reading_time_minutes">
<item quantity="one">%d min</item>
<item quantity="other">%d min</item>
</plurals>
<string name="settings_option_auto_delete_articles_subtitle">Delete read, unstarred articles older than 3 months</string>
<string name="settings_option_auto_delete_articles_title">Auto Delete Articles</string>
<string name="refresh_every_day">Every day</string>
Expand Down
6 changes: 4 additions & 2 deletions capy/src/main/java/com/jocmp/capy/Account.kt
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ data class Account(
feedbin = Feedbin.forAccount(
path = cacheDirectory,
preferences = preferences
)
),
preferences = preferences,
)

Source.MINIFLUX,
Expand All @@ -82,7 +83,8 @@ data class Account(
path = cacheDirectory,
preferences = preferences,
source = source
)
),
preferences = preferences,
)

Source.FRESHRSS,
Expand Down
3 changes: 3 additions & 0 deletions capy/src/main/java/com/jocmp/capy/AccountPreferences.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,7 @@ class AccountPreferences(

val keywordBlocklist: Preference<Set<String>>
get() = store.getStringSet("keyword_blocklist")

val showReadingTime: Preference<Boolean>
get() = store.getBoolean("show_reading_time", false)
}
1 change: 1 addition & 0 deletions capy/src/main/java/com/jocmp/capy/Article.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ data class Article(
val openInBrowser: Boolean = false,
val fullContent: FullContentState = FullContentState.NONE,
val content: String = contentHTML.ifBlank { summary },
val readingTimeMinutes: Long? = null,
val enclosures: List<Enclosure> = emptyList(),
val enclosureType: EnclosureType? = null,
) {
Expand Down
Loading