diff --git a/core/data/src/commonMain/kotlin/dev/sasikanth/rss/reader/data/repository/RssRepository.kt b/core/data/src/commonMain/kotlin/dev/sasikanth/rss/reader/data/repository/RssRepository.kt index c5ed0e15c..8c46948b8 100644 --- a/core/data/src/commonMain/kotlin/dev/sasikanth/rss/reader/data/repository/RssRepository.kt +++ b/core/data/src/commonMain/kotlin/dev/sasikanth/rss/reader/data/repository/RssRepository.kt @@ -496,6 +496,14 @@ class RssRepository( withContext(ioDispatcher) { postQueries.markPostsAsRead(sourceId = null, after = postsAfter) } } + suspend fun markPostsAsRead(postIds: Set) { + withContext(ioDispatcher) { + transactionRunner.invoke { + postIds.forEach { postId -> postQueries.updateReadStatus(read = true, id = postId) } + } + } + } + suspend fun markPostsInFeedAsRead( feedIds: List, postsAfter: Instant = Instant.DISTANT_PAST diff --git a/core/data/src/commonMain/kotlin/dev/sasikanth/rss/reader/data/repository/SettingsRepository.kt b/core/data/src/commonMain/kotlin/dev/sasikanth/rss/reader/data/repository/SettingsRepository.kt index 17b2f5b0a..de3167c85 100644 --- a/core/data/src/commonMain/kotlin/dev/sasikanth/rss/reader/data/repository/SettingsRepository.kt +++ b/core/data/src/commonMain/kotlin/dev/sasikanth/rss/reader/data/repository/SettingsRepository.kt @@ -42,6 +42,7 @@ class SettingsRepository(private val dataStore: DataStore) { 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 = dataStore.data.map { preferences -> @@ -83,6 +84,9 @@ class SettingsRepository(private val dataStore: DataStore) { val showFeedFavIcon: Flow = dataStore.data.map { preferences -> preferences[showFeedFavIconKey] ?: true } + val markAsReadOn: Flow = + dataStore.data.map { preferences -> mapToMarkAsReadOnType(preferences[markPostsAsReadOnKey]) } + suspend fun enableAutoSyncImmediate(): Boolean { return enableAutoSync.first() } @@ -131,6 +135,10 @@ class SettingsRepository(private val dataStore: DataStore) { 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) @@ -160,6 +168,11 @@ class SettingsRepository(private val dataStore: DataStore) { 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 { @@ -180,3 +193,8 @@ enum class Period { SIX_MONTHS, ONE_YEAR } + +enum class MarkAsReadOn { + Open, + Scroll +} diff --git a/resources/strings/src/commonMain/kotlin/dev/sasikanth/rss/reader/resources/strings/DeTwineStrings.kt b/resources/strings/src/commonMain/kotlin/dev/sasikanth/rss/reader/resources/strings/DeTwineStrings.kt index 5d5dd5850..5ec85c828 100644 --- a/resources/strings/src/commonMain/kotlin/dev/sasikanth/rss/reader/resources/strings/DeTwineStrings.kt +++ b/resources/strings/src/commonMain/kotlin/dev/sasikanth/rss/reader/resources/strings/DeTwineStrings.kt @@ -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", ) diff --git a/resources/strings/src/commonMain/kotlin/dev/sasikanth/rss/reader/resources/strings/EnTwineStrings.kt b/resources/strings/src/commonMain/kotlin/dev/sasikanth/rss/reader/resources/strings/EnTwineStrings.kt index 8ea49b251..f7f5d84c9 100644 --- a/resources/strings/src/commonMain/kotlin/dev/sasikanth/rss/reader/resources/strings/EnTwineStrings.kt +++ b/resources/strings/src/commonMain/kotlin/dev/sasikanth/rss/reader/resources/strings/EnTwineStrings.kt @@ -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", ) diff --git a/resources/strings/src/commonMain/kotlin/dev/sasikanth/rss/reader/resources/strings/TrTwineStrings.kt b/resources/strings/src/commonMain/kotlin/dev/sasikanth/rss/reader/resources/strings/TrTwineStrings.kt index ab38f7a98..4da5b575e 100644 --- a/resources/strings/src/commonMain/kotlin/dev/sasikanth/rss/reader/resources/strings/TrTwineStrings.kt +++ b/resources/strings/src/commonMain/kotlin/dev/sasikanth/rss/reader/resources/strings/TrTwineStrings.kt @@ -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", ) diff --git a/resources/strings/src/commonMain/kotlin/dev/sasikanth/rss/reader/resources/strings/TwineStrings.kt b/resources/strings/src/commonMain/kotlin/dev/sasikanth/rss/reader/resources/strings/TwineStrings.kt index 34c439182..c8baa0d6c 100644 --- a/resources/strings/src/commonMain/kotlin/dev/sasikanth/rss/reader/resources/strings/TwineStrings.kt +++ b/resources/strings/src/commonMain/kotlin/dev/sasikanth/rss/reader/resources/strings/TwineStrings.kt @@ -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 { diff --git a/resources/strings/src/commonMain/kotlin/dev/sasikanth/rss/reader/resources/strings/ZhTwineStrings.kt b/resources/strings/src/commonMain/kotlin/dev/sasikanth/rss/reader/resources/strings/ZhTwineStrings.kt index aa9d3c93f..299c2c9da 100644 --- a/resources/strings/src/commonMain/kotlin/dev/sasikanth/rss/reader/resources/strings/ZhTwineStrings.kt +++ b/resources/strings/src/commonMain/kotlin/dev/sasikanth/rss/reader/resources/strings/ZhTwineStrings.kt @@ -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", ) diff --git a/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/home/HomeEvent.kt b/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/home/HomeEvent.kt index e0c80263f..00f4a68d2 100644 --- a/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/home/HomeEvent.kt +++ b/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/home/HomeEvent.kt @@ -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) : HomeEvent + + data object MarkScrolledPostsAsRead : HomeEvent + + data class MarkFeaturedPostsAsRead(val postId: String) : HomeEvent } diff --git a/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/home/HomePresenter.kt b/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/home/HomePresenter.kt index ac8bbf04a..d19ffbbe2 100644 --- a/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/home/HomePresenter.kt +++ b/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/home/HomePresenter.kt @@ -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 @@ -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 @@ -178,6 +178,7 @@ class HomePresenter( ) : InstanceKeeper.Instance { private val coroutineScope = CoroutineScope(SupervisorJob() + dispatchersProvider.main) + private val scrolledPostItems = mutableSetOf() private val _state = MutableStateFlow(HomeState.DEFAULT) val state: StateFlow = @@ -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) { + scrolledPostItems += postIds + } + private fun markPostsAsRead(source: Source?) { coroutineScope.launch { val postsAfter = getPostsAfter(_state.value.postsType) diff --git a/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/home/ui/HomeScreen.kt b/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/home/ui/HomeScreen.kt index 315833e8b..23b0853e6 100644 --- a/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/home/ui/HomeScreen.kt +++ b/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/home/ui/HomeScreen.kt @@ -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 @@ -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, @@ -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 diff --git a/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/home/ui/PostList.kt b/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/home/ui/PostList.kt index 24472094a..03c3cbf94 100644 --- a/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/home/ui/PostList.kt +++ b/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/home/ui/PostList.kt @@ -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, @@ -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( diff --git a/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/settings/SettingsEvent.kt b/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/settings/SettingsEvent.kt index 2d27f4b83..0240516da 100644 --- a/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/settings/SettingsEvent.kt +++ b/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/settings/SettingsEvent.kt @@ -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 { @@ -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 } diff --git a/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/settings/SettingsPresenter.kt b/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/settings/SettingsPresenter.kt index adb7f0211..f1e53db00 100644 --- a/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/settings/SettingsPresenter.kt +++ b/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/settings/SettingsPresenter.kt @@ -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 @@ -114,7 +115,8 @@ class SettingsPresenter( settingsRepository.showReaderView, settingsRepository.appThemeMode, settingsRepository.enableAutoSync, - settingsRepository.showFeedFavIcon + settingsRepository.showFeedFavIcon, + settingsRepository.markAsReadOn ) { browserType, showUnreadPostsCount, @@ -122,7 +124,8 @@ class SettingsPresenter( showReaderView, appThemeMode, enableAutoSync, - showFeedFavIcon -> + showFeedFavIcon, + markAsReadOn -> Settings( browserType = browserType, showUnreadPostsCount = showUnreadPostsCount, @@ -131,6 +134,7 @@ class SettingsPresenter( appThemeMode = appThemeMode, enableAutoSync = enableAutoSync, showFeedFavIcon = showFeedFavIcon, + markAsReadOn = markAsReadOn, ) } .onEach { settings -> @@ -143,6 +147,7 @@ class SettingsPresenter( appThemeMode = settings.appThemeMode, enableAutoSync = settings.enableAutoSync, showFeedFavIcon = settings.showFeedFavIcon, + markAsReadOn = settings.markAsReadOn ) } } @@ -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) } } @@ -236,4 +246,5 @@ private data class Settings( val appThemeMode: AppThemeMode, val enableAutoSync: Boolean, val showFeedFavIcon: Boolean, + val markAsReadOn: MarkAsReadOn, ) diff --git a/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/settings/SettingsState.kt b/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/settings/SettingsState.kt index 68a40b4bb..3aa5d9203 100644 --- a/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/settings/SettingsState.kt +++ b/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/settings/SettingsState.kt @@ -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 @@ -34,6 +35,7 @@ internal data class SettingsState( val appThemeMode: AppThemeMode, val enableAutoSync: Boolean, val showFeedFavIcon: Boolean, + val markAsReadOn: MarkAsReadOn, ) { companion object { @@ -50,6 +52,7 @@ internal data class SettingsState( appThemeMode = AppThemeMode.Auto, enableAutoSync = true, showFeedFavIcon = true, + markAsReadOn = MarkAsReadOn.Open ) } } diff --git a/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/settings/ui/SettingsScreen.kt b/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/settings/ui/SettingsScreen.kt index fec79d565..06c22de57 100644 --- a/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/settings/ui/SettingsScreen.kt +++ b/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/settings/ui/SettingsScreen.kt @@ -76,6 +76,7 @@ import dev.sasikanth.rss.reader.components.ToggleableButtonItem import dev.sasikanth.rss.reader.components.image.AsyncImage 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.Period.ONE_MONTH import dev.sasikanth.rss.reader.data.repository.Period.ONE_WEEK @@ -248,6 +249,14 @@ internal fun SettingsScreen( item { Divider(24.dp) } + item { + MarkArticleAsReadOnSetting(articleMarkAsReadOn = state.markAsReadOn) { + settingsPresenter.dispatch(SettingsEvent.MarkAsReadOnChanged(it)) + } + } + + item { Divider(24.dp) } + item { PostsDeletionPeriodSettingItem( postsDeletionPeriod = state.postsDeletionPeriod, @@ -301,6 +310,98 @@ internal fun SettingsScreen( ) } +@Composable +private fun MarkArticleAsReadOnSetting( + articleMarkAsReadOn: MarkAsReadOn, + onMarkAsReadOnChanged: (MarkAsReadOn) -> Unit +) { + var showDropdown by remember { mutableStateOf(false) } + + Row( + modifier = Modifier.padding(horizontal = 24.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + modifier = Modifier.weight(1f), + text = LocalStrings.current.markArticleAsRead, + style = MaterialTheme.typography.titleMedium, + color = AppTheme.colorScheme.textEmphasisHigh + ) + + Box { + val density = LocalDensity.current + var buttonHeight by remember { mutableStateOf(Dp.Unspecified) } + + TextButton( + modifier = + Modifier.onGloballyPositioned { coordinates -> + buttonHeight = with(density) { coordinates.size.height.toDp() } + }, + onClick = { showDropdown = true }, + shape = MaterialTheme.shapes.medium + ) { + val markAsReadOnLabel = + when (articleMarkAsReadOn) { + MarkAsReadOn.Open -> LocalStrings.current.markArticleAsReadOnOpen + MarkAsReadOn.Scroll -> LocalStrings.current.markArticleAsReadOnScroll + } + + Text( + text = markAsReadOnLabel, + style = MaterialTheme.typography.labelLarge, + color = AppTheme.colorScheme.tintedForeground + ) + + Spacer(Modifier.requiredWidth(8.dp)) + + Icon( + imageVector = Icons.Filled.ExpandMore, + contentDescription = null, + tint = AppTheme.colorScheme.tintedForeground + ) + } + + DropdownMenu( + offset = DpOffset(0.dp, buttonHeight.unaryMinus()), + expanded = showDropdown, + onDismissRequest = { showDropdown = false }, + ) { + MarkAsReadOn.entries.forEach { markAsReadOn -> + val label = + when (markAsReadOn) { + MarkAsReadOn.Open -> LocalStrings.current.markArticleAsReadOnOpen + MarkAsReadOn.Scroll -> LocalStrings.current.markArticleAsReadOnScroll + } + + val backgroundColor = + if (markAsReadOn == articleMarkAsReadOn) { + AppTheme.colorScheme.tintedHighlight + } else { + Color.Unspecified + } + + DropdownMenuItem( + onClick = { + onMarkAsReadOnChanged(markAsReadOn) + showDropdown = false + }, + modifier = Modifier.background(backgroundColor) + ) { + val textColor = + if (markAsReadOn == articleMarkAsReadOn) { + AppTheme.colorScheme.onSurface + } else { + AppTheme.colorScheme.textEmphasisHigh + } + + Text(text = label, style = MaterialTheme.typography.bodyLarge, color = textColor) + } + } + } + } + } +} + @Composable private fun BlockedWordsSettingItem(onClick: () -> Unit) { Row( diff --git a/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/utils/CoroutinesExt.kt b/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/utils/CoroutinesExt.kt index 2cbecd068..2cf790e70 100644 --- a/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/utils/CoroutinesExt.kt +++ b/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/utils/CoroutinesExt.kt @@ -18,30 +18,7 @@ package dev.sasikanth.rss.reader.utils import kotlinx.coroutines.flow.Flow -inline fun combine( - flow: Flow, - flow2: Flow, - flow3: Flow, - flow4: Flow, - flow5: Flow, - flow6: Flow, - crossinline transform: suspend (T1, T2, T3, T4, T5, T6) -> R -): Flow { - return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6) { args: Array<*> - -> - @Suppress("UNCHECKED_CAST") - transform( - args[0] as T1, - args[1] as T2, - args[2] as T3, - args[3] as T4, - args[4] as T5, - args[5] as T6, - ) - } -} - -inline fun combine( +inline fun combine( flow: Flow, flow2: Flow, flow3: Flow, @@ -49,9 +26,10 @@ inline fun combine( flow5: Flow, flow6: Flow, flow7: Flow, - crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7) -> R + flow8: Flow, + crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8) -> R ): Flow { - return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6, flow7) { + return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6, flow7, flow8) { args: Array<*> -> @Suppress("UNCHECKED_CAST") transform( @@ -62,6 +40,7 @@ inline fun combine( args[4] as T5, args[5] as T6, args[6] as T7, + args[7] as T8, ) } }