Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[1.261.*] Pre-release merge #847

Merged
merged 1 commit into from
Feb 28, 2025
Merged
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 @@ -496,6 +496,14 @@ class RssRepository(
withContext(ioDispatcher) { postQueries.markPostsAsRead(sourceId = null, after = postsAfter) }
}

suspend fun markPostsAsRead(postIds: Set<String>) {
withContext(ioDispatcher) {
transactionRunner.invoke {
postIds.forEach { postId -> postQueries.updateReadStatus(read = true, id = postId) }
}
}
}

suspend fun markPostsInFeedAsRead(
feedIds: List<String>,
postsAfter: Instant = Instant.DISTANT_PAST
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class SettingsRepository(private val dataStore: DataStore<Preferences>) {
private val appThemeModeKey = stringPreferencesKey("pref_app_theme_mode_v2")
private val enableAutoSyncKey = booleanPreferencesKey("enable_auto_sync")
private val showFeedFavIconKey = booleanPreferencesKey("show_feed_fav_icon")
private val markPostsAsReadOnKey = stringPreferencesKey("mark_posts_as_read_on")

val browserType: Flow<BrowserType> =
dataStore.data.map { preferences ->
Expand Down Expand Up @@ -83,6 +84,9 @@ class SettingsRepository(private val dataStore: DataStore<Preferences>) {
val showFeedFavIcon: Flow<Boolean> =
dataStore.data.map { preferences -> preferences[showFeedFavIconKey] ?: true }

val markAsReadOn: Flow<MarkAsReadOn> =
dataStore.data.map { preferences -> mapToMarkAsReadOnType(preferences[markPostsAsReadOnKey]) }

suspend fun enableAutoSyncImmediate(): Boolean {
return enableAutoSync.first()
}
Expand Down Expand Up @@ -131,6 +135,10 @@ class SettingsRepository(private val dataStore: DataStore<Preferences>) {
dataStore.edit { preferences -> preferences[showFeedFavIconKey] = value }
}

suspend fun updateMarkAsReadOn(value: MarkAsReadOn) {
dataStore.edit { preferences -> preferences[markPostsAsReadOnKey] = value.name }
}

private fun mapToAppThemeMode(pref: String?): AppThemeMode? {
if (pref.isNullOrBlank()) return null
return AppThemeMode.valueOf(pref)
Expand Down Expand Up @@ -160,6 +168,11 @@ class SettingsRepository(private val dataStore: DataStore<Preferences>) {
if (pref.isNullOrBlank()) return null
return PostsType.valueOf(pref)
}

private fun mapToMarkAsReadOnType(pref: String?): MarkAsReadOn {
if (pref.isNullOrBlank()) return MarkAsReadOn.Open
return MarkAsReadOn.valueOf(pref)
}
}

enum class AppThemeMode {
Expand All @@ -180,3 +193,8 @@ enum class Period {
SIX_MONTHS,
ONE_YEAR
}

enum class MarkAsReadOn {
Open,
Scroll
}
Original file line number Diff line number Diff line change
Expand Up @@ -179,4 +179,7 @@ val DeTwineStrings =
blockedWordsDesc =
"Post can be hidden from home screen based on their text. We recommend avoiding common words that appear in many posts, since it can result in no posts being shown or negatively impacting app performance. \n\nHidden posts will still be displayed in search & bookmarks.",
blockedWordsEmpty = "You haven't blocked any words yet",
markArticleAsRead = "Mark article as read",
markArticleAsReadOnOpen = "On Open",
markArticleAsReadOnScroll = "On Scroll",
)
Original file line number Diff line number Diff line change
Expand Up @@ -186,4 +186,7 @@ val EnTwineStrings =
blockedWordsDesc =
"Post can be hidden from the home screen based on their text. We recommend avoiding common words that appear in many posts, since it can result in no posts being shown or negatively impacting app performance. \n\nHidden posts will still be displayed in search & bookmarks.",
blockedWordsEmpty = "You haven't blocked any words yet",
markArticleAsRead = "Mark article as read",
markArticleAsReadOnOpen = "On Open",
markArticleAsReadOnScroll = "On Scroll",
)
Original file line number Diff line number Diff line change
Expand Up @@ -175,4 +175,7 @@ val TrTwineStrings =
blockedWordsDesc =
"Post can be hidden from the home screen based on their text. We recommend avoiding common words that appear in many posts, since it can result in no posts being shown or negatively impacting app performance. \n\nHidden posts will still be displayed in search & bookmarks.",
blockedWordsEmpty = "You haven't blocked any words yet",
markArticleAsRead = "Mark article as read",
markArticleAsReadOnOpen = "On Open",
markArticleAsReadOnScroll = "On Scroll",
)
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,9 @@ data class TwineStrings(
val blockedWordsHint: String,
val blockedWordsDesc: String,
val blockedWordsEmpty: String,
val markArticleAsRead: String,
val markArticleAsReadOnOpen: String,
val markArticleAsReadOnScroll: String,
)

object Locales {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,4 +166,7 @@ val ZhTwineStrings =
blockedWordsDesc =
"Post can be hidden from the home screen based on their text. We recommend avoiding common words that appear in many posts, since it can result in no posts being shown or negatively impacting app performance. \n\nHidden posts will still be displayed in search & bookmarks.",
blockedWordsEmpty = "You haven't blocked any words yet",
markArticleAsRead = "Mark article as read",
markArticleAsReadOnOpen = "On Open",
markArticleAsReadOnScroll = "On Scroll",
)
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,10 @@ sealed interface HomeEvent {
data class TogglePostReadStatus(val postId: String, val postRead: Boolean) : HomeEvent

data class MarkPostsAsRead(val source: Source?) : HomeEvent

data class OnPostItemsScrolled(val postIds: List<String>) : HomeEvent

data object MarkScrolledPostsAsRead : HomeEvent

data class MarkFeaturedPostsAsRead(val postId: String) : HomeEvent
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import dev.sasikanth.rss.reader.core.model.local.FeedGroup
import dev.sasikanth.rss.reader.core.model.local.PostWithMetadata
import dev.sasikanth.rss.reader.core.model.local.PostsType
import dev.sasikanth.rss.reader.core.model.local.Source
import dev.sasikanth.rss.reader.data.repository.MarkAsReadOn
import dev.sasikanth.rss.reader.data.repository.ObservableActiveSource
import dev.sasikanth.rss.reader.data.repository.RssRepository
import dev.sasikanth.rss.reader.data.repository.SettingsRepository
Expand All @@ -55,10 +56,9 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
Expand Down Expand Up @@ -178,6 +178,7 @@ class HomePresenter(
) : InstanceKeeper.Instance {

private val coroutineScope = CoroutineScope(SupervisorJob() + dispatchersProvider.main)
private val scrolledPostItems = mutableSetOf<String>()

private val _state = MutableStateFlow(HomeState.DEFAULT)
val state: StateFlow<HomeState> =
Expand Down Expand Up @@ -215,9 +216,37 @@ class HomePresenter(
is HomeEvent.OnPostsTypeChanged -> onPostsTypeChanged(event.postsType)
is HomeEvent.TogglePostReadStatus -> togglePostReadStatus(event.postId, event.postRead)
is HomeEvent.MarkPostsAsRead -> markPostsAsRead(event.source)
is HomeEvent.OnPostItemsScrolled -> onPostItemsScrolled(event.postIds)
HomeEvent.MarkScrolledPostsAsRead -> markScrolledPostsAsRead()
is HomeEvent.MarkFeaturedPostsAsRead -> markFeaturedPostAsRead(event.postId)
}
}

private fun markFeaturedPostAsRead(postId: String) {
coroutineScope.launch {
val markPostsAsReadOn = settingsRepository.markAsReadOn.first()

if (markPostsAsReadOn != MarkAsReadOn.Scroll) return@launch

rssRepository.updatePostReadStatus(read = true, id = postId)
}
}

private fun markScrolledPostsAsRead() {
coroutineScope.launch {
val markPostsAsReadOn = settingsRepository.markAsReadOn.first()

if (markPostsAsReadOn != MarkAsReadOn.Scroll) return@launch

rssRepository.markPostsAsRead(postIds = scrolledPostItems)
scrolledPostItems.clear()
}
}

private fun onPostItemsScrolled(postIds: List<String>) {
scrolledPostItems += postIds
}

private fun markPostsAsRead(source: Source?) {
coroutineScope.launch {
val postsAfter = getPostsAfter(_state.value.postsType)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
Expand Down Expand Up @@ -84,11 +85,16 @@ import dev.sasikanth.rss.reader.resources.strings.LocalStrings
import dev.sasikanth.rss.reader.ui.AppTheme
import dev.sasikanth.rss.reader.ui.LocalDynamicColorState
import dev.sasikanth.rss.reader.utils.inverse
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch

internal val BOTTOM_SHEET_PEEK_HEIGHT = 96.dp
private val BOTTOM_SHEET_CORNER_SIZE = 32.dp

@OptIn(FlowPreview::class)
@Composable
internal fun HomeScreen(
homePresenter: HomePresenter,
Expand Down Expand Up @@ -145,6 +151,34 @@ internal fun HomeScreen(
}
}

LaunchedEffect(listState) {
snapshotFlow { listState.layoutInfo.visibleItemsInfo }
.onEach { items ->
val postIds =
items
.filter { it.contentType == "post_item" && it.key is String }
.map { it.key as String }

homePresenter.dispatch(HomeEvent.OnPostItemsScrolled(postIds))
}
.debounce(2.seconds)
.collect { homePresenter.dispatch(HomeEvent.MarkScrolledPostsAsRead) }
}

LaunchedEffect(featuredPostsPagerState) {
snapshotFlow { featuredPostsPagerState.settledPage }
.debounce(2.seconds)
.collect {
val featuredPost = state.featuredPosts?.get(it) ?: return@collect

if (featuredPost.postWithMetadata.read) return@collect

homePresenter.dispatch(
HomeEvent.MarkFeaturedPostsAsRead(postId = featuredPost.postWithMetadata.id)
)
}
}

AppTheme(useDarkTheme = true) {
Scaffold(modifier) { scaffoldPadding ->
val density = LocalDensity.current
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ internal fun PostsList(
PaddingValues(top = topContentPadding, bottom = BOTTOM_SHEET_PEEK_HEIGHT + 120.dp)
) {
if (featuredPosts.isNotEmpty()) {
item {
item(contentType = "featured_items") {
FeaturedSection(
paddingValues = paddingValues,
pagerState = featuredPostsPagerState,
Expand All @@ -108,7 +108,11 @@ internal fun PostsList(
}
}

items(posts.itemCount) { index ->
items(
count = posts.itemCount,
key = { index -> posts.peek(index)?.id ?: index },
contentType = { "post_item" }
) { index ->
val post = posts[index]
if (post != null) {
PostListItem(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package dev.sasikanth.rss.reader.settings

import dev.sasikanth.rss.reader.data.repository.AppThemeMode
import dev.sasikanth.rss.reader.data.repository.BrowserType
import dev.sasikanth.rss.reader.data.repository.MarkAsReadOn
import dev.sasikanth.rss.reader.data.repository.Period

sealed interface SettingsEvent {
Expand Down Expand Up @@ -46,4 +47,6 @@ sealed interface SettingsEvent {
data class OnAppThemeModeChanged(val appThemeMode: AppThemeMode) : SettingsEvent

data object BlockedWordsClicked : SettingsEvent

data class MarkAsReadOnChanged(val newMarkAsReadOn: MarkAsReadOn) : SettingsEvent
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import com.arkivanov.essenty.instancekeeper.getOrCreate
import dev.sasikanth.rss.reader.app.AppInfo
import dev.sasikanth.rss.reader.data.repository.AppThemeMode
import dev.sasikanth.rss.reader.data.repository.BrowserType
import dev.sasikanth.rss.reader.data.repository.MarkAsReadOn
import dev.sasikanth.rss.reader.data.repository.Period
import dev.sasikanth.rss.reader.data.repository.RssRepository
import dev.sasikanth.rss.reader.data.repository.SettingsRepository
Expand Down Expand Up @@ -114,15 +115,17 @@ class SettingsPresenter(
settingsRepository.showReaderView,
settingsRepository.appThemeMode,
settingsRepository.enableAutoSync,
settingsRepository.showFeedFavIcon
settingsRepository.showFeedFavIcon,
settingsRepository.markAsReadOn
) {
browserType,
showUnreadPostsCount,
postsDeletionPeriod,
showReaderView,
appThemeMode,
enableAutoSync,
showFeedFavIcon ->
showFeedFavIcon,
markAsReadOn ->
Settings(
browserType = browserType,
showUnreadPostsCount = showUnreadPostsCount,
Expand All @@ -131,6 +134,7 @@ class SettingsPresenter(
appThemeMode = appThemeMode,
enableAutoSync = enableAutoSync,
showFeedFavIcon = showFeedFavIcon,
markAsReadOn = markAsReadOn,
)
}
.onEach { settings ->
Expand All @@ -143,6 +147,7 @@ class SettingsPresenter(
appThemeMode = settings.appThemeMode,
enableAutoSync = settings.enableAutoSync,
showFeedFavIcon = settings.showFeedFavIcon,
markAsReadOn = settings.markAsReadOn
)
}
}
Expand Down Expand Up @@ -179,9 +184,14 @@ class SettingsPresenter(
SettingsEvent.BlockedWordsClicked -> {
// no-op
}
is SettingsEvent.MarkAsReadOnChanged -> markAsReadOnChanged(event.newMarkAsReadOn)
}
}

private fun markAsReadOnChanged(markAsReadOn: MarkAsReadOn) {
coroutineScope.launch { settingsRepository.updateMarkAsReadOn(markAsReadOn) }
}

private fun toggleShowFeedFavIcon(value: Boolean) {
coroutineScope.launch { settingsRepository.toggleShowFeedFavIcon(value) }
}
Expand Down Expand Up @@ -236,4 +246,5 @@ private data class Settings(
val appThemeMode: AppThemeMode,
val enableAutoSync: Boolean,
val showFeedFavIcon: Boolean,
val markAsReadOn: MarkAsReadOn,
)
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import androidx.compose.runtime.Immutable
import dev.sasikanth.rss.reader.app.AppInfo
import dev.sasikanth.rss.reader.data.repository.AppThemeMode
import dev.sasikanth.rss.reader.data.repository.BrowserType
import dev.sasikanth.rss.reader.data.repository.MarkAsReadOn
import dev.sasikanth.rss.reader.data.repository.Period
import dev.sasikanth.rss.reader.opml.OpmlResult

Expand All @@ -34,6 +35,7 @@ internal data class SettingsState(
val appThemeMode: AppThemeMode,
val enableAutoSync: Boolean,
val showFeedFavIcon: Boolean,
val markAsReadOn: MarkAsReadOn,
) {

companion object {
Expand All @@ -50,6 +52,7 @@ internal data class SettingsState(
appThemeMode = AppThemeMode.Auto,
enableAutoSync = true,
showFeedFavIcon = true,
markAsReadOn = MarkAsReadOn.Open
)
}
}
Loading