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

Show unread article count in title #300

Merged
merged 3 commits into from
Jun 10, 2024
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
74 changes: 40 additions & 34 deletions app/src/main/java/com/nononsenseapps/feeder/archmodel/Repository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.launch
import org.kodein.di.DI
Expand Down Expand Up @@ -276,6 +275,10 @@ class Repository(override val di: DI) : DIAware {
sessionStore.setResumeTime(value)
}

val showTitleUnreadCount = settingsStore.showTitleUnreadCount

fun setShowTitleUnreadCount(value: Boolean) = settingsStore.setShowTitleUnreadCount(value)

/**
* Returns true if the latest sync timestamp is within the last 10 seconds
*/
Expand Down Expand Up @@ -416,44 +419,46 @@ class Repository(override val di: DI) : DIAware {
fun getScreenTitleForFeedOrTag(
feedId: Long,
tag: String,
) = flow {
emit(
ScreenTitle(
title =
when {
feedId > ID_UNSET -> feedStore.getDisplayTitle(feedId)
tag.isNotBlank() -> tag
else -> null
},
type =
when (feedId) {
ID_UNSET -> FeedType.TAG
ID_ALL_FEEDS -> FeedType.ALL_FEEDS
ID_SAVED_ARTICLES -> FeedType.SAVED_ARTICLES
else -> FeedType.FEED
},
),
) = getUnreadCount(feedId).mapLatest { unreadCount ->
ScreenTitle(
title =
when {
feedId > ID_UNSET -> feedStore.getDisplayTitle(feedId)
tag.isNotBlank() -> tag
else -> null
},
type =
when (feedId) {
ID_UNSET -> FeedType.TAG
ID_ALL_FEEDS -> FeedType.ALL_FEEDS
ID_SAVED_ARTICLES -> FeedType.SAVED_ARTICLES
else -> FeedType.FEED
},
unreadCount = unreadCount,
)
}

@OptIn(ExperimentalCoroutinesApi::class)
fun getScreenTitleForCurrentFeedOrTag(): Flow<ScreenTitle> =
currentFeedAndTag.mapLatest { (feedId, tag) ->
ScreenTitle(
title =
when {
feedId > ID_UNSET -> feedStore.getDisplayTitle(feedId)
tag.isNotBlank() -> tag
else -> null
},
type =
when (feedId) {
ID_UNSET -> FeedType.TAG
ID_ALL_FEEDS -> FeedType.ALL_FEEDS
ID_SAVED_ARTICLES -> FeedType.SAVED_ARTICLES
else -> FeedType.FEED
},
)
currentFeedAndTag.flatMapLatest { (feedId, tag) ->
getUnreadCount(feedId).mapLatest { unreadCount ->
ScreenTitle(
title =
when {
feedId > ID_UNSET -> feedStore.getDisplayTitle(feedId)
tag.isNotBlank() -> tag
else -> null
},
type =
when (feedId) {
ID_UNSET -> FeedType.TAG
ID_ALL_FEEDS -> FeedType.ALL_FEEDS
ID_SAVED_ARTICLES -> FeedType.SAVED_ARTICLES
else -> FeedType.FEED
},
unreadCount = unreadCount,
)
}
}

suspend fun deleteFeeds(feedIds: List<Long>) {
Expand Down Expand Up @@ -834,6 +839,7 @@ private data class FeedListArgs(
data class ScreenTitle(
val title: String?,
val type: FeedType,
val unreadCount: Int,
)

enum class FeedType {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,14 @@ class SettingsStore(override val di: DI) : DIAware {
}
}

private val _showTitleUnreadCount = MutableStateFlow(sp.getBoolean(PREF_SHOW_TITLE_UNREAD_COUNT, false))
val showTitleUnreadCount = _showTitleUnreadCount.asStateFlow()

fun setShowTitleUnreadCount(value: Boolean) {
_showTitleUnreadCount.value = value
sp.edit().putBoolean(PREF_SHOW_TITLE_UNREAD_COUNT, value).apply()
}

fun getAllSettings(): Map<String, String> {
val all = sp.all ?: emptyMap()

Expand Down Expand Up @@ -578,6 +586,11 @@ const val PREF_LIST_SHOW_READING_TIME = "pref_show_reading_time"
*/
const val PREF_READALOUD_USE_DETECT_LANGUAGE = "pref_readaloud_detect_lang"

/**
* Appearance settings
*/
const val PREF_SHOW_TITLE_UNREAD_COUNT = "pref_show_title_unread_count"

/**
* Used for OPML Import/Export. Please add new (only) user configurable settings here
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -996,10 +996,19 @@ fun FeedScreen(
scrollBehavior = scrollBehavior,
title =
when (viewState.feedScreenTitle.type) {
FeedType.FEED -> viewState.feedScreenTitle.title
FeedType.TAG -> viewState.feedScreenTitle.title
FeedType.FEED, FeedType.TAG -> viewState.feedScreenTitle.title
FeedType.SAVED_ARTICLES -> stringResource(id = R.string.saved_articles)
FeedType.ALL_FEEDS -> stringResource(id = R.string.all_feeds)
}.let { title ->
if (viewState.feedScreenTitle.type == FeedType.SAVED_ARTICLES ||
!viewState.showTitleUnreadCount ||
viewState.feedScreenTitle.unreadCount == 0 ||
title == null
) {
title
} else {
title + " ${stringResource(id = R.string.title_unread_count, viewState.feedScreenTitle.unreadCount)}"
}
} ?: "",
navigationIcon = {
IconButton(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ class FeedArticleViewModel(
.stateIn(
viewModelScope,
SharingStarted.Eagerly,
ScreenTitle("", FeedType.ALL_FEEDS),
ScreenTitle("", FeedType.ALL_FEEDS, 0),
)

private val visibleFeeds: StateFlow<List<FeedTitle>> =
Expand Down Expand Up @@ -308,6 +308,7 @@ class FeedArticleViewModel(
repository.showOnlyTitle,
repository.showReadingTime,
repository.syncWorkerRunning,
repository.showTitleUnreadCount,
) { params: Array<Any> ->
val article = params[15] as Article

Expand Down Expand Up @@ -374,6 +375,7 @@ class FeedArticleViewModel(
},
image = article.image,
articleContent = parseArticleContent(article, textToDisplay),
showTitleUnreadCount = params[28] as Boolean,
)
}
.stateIn(
Expand Down Expand Up @@ -608,6 +610,7 @@ interface FeedScreenViewState {
val showReadingTime: Boolean
val filter: FeedListFilter
val showFilterMenu: Boolean
val showTitleUnreadCount: Boolean
}

@Immutable
Expand Down Expand Up @@ -702,7 +705,7 @@ data class FeedArticleScreenViewState(
override val currentTheme: ThemeOptions = ThemeOptions.SYSTEM,
override val currentlySyncing: Boolean = false,
// Defaults to empty string to avoid rendering until loading complete
override val feedScreenTitle: ScreenTitle = ScreenTitle("", FeedType.FEED),
override val feedScreenTitle: ScreenTitle = ScreenTitle("", FeedType.FEED, 0),
override val visibleFeeds: List<FeedTitle> = emptyList(),
override val feedItemStyle: FeedItemStyle = FeedItemStyle.CARD,
override val expandedTags: Set<String> = emptySet(),
Expand Down Expand Up @@ -739,6 +742,7 @@ data class FeedArticleScreenViewState(
override val wordCount: Int = 0,
override val image: ThumbnailImage? = null,
override val articleContent: LinearArticle = LinearArticle(elements = emptyList()),
override val showTitleUnreadCount: Boolean = false,
) : FeedScreenViewState, ArticleScreenViewState

sealed class TSSError
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@ fun SettingsScreen(
onOpenAdjacent = settingsViewModel::setIsOpenAdjacent,
showReadingTime = viewState.showReadingTime,
onShowReadingTimeChange = settingsViewModel::setShowReadingTime,
showTitleUnreadCount = viewState.showTitleUnreadCount,
onShowTitleUnreadCountChange = settingsViewModel::setShowTitleUnreadCount,
onStartActivity = { intent ->
activityLauncher.startActivity(false, intent)
},
Expand Down Expand Up @@ -268,6 +270,8 @@ private fun SettingsScreenPreview() {
onOpenAdjacent = {},
showReadingTime = false,
onShowReadingTimeChange = {},
showTitleUnreadCount = false,
onShowTitleUnreadCountChange = {},
onStartActivity = {},
modifier = Modifier,
)
Expand Down Expand Up @@ -329,6 +333,8 @@ fun SettingsList(
onOpenAdjacent: (Boolean) -> Unit,
showReadingTime: Boolean,
onShowReadingTimeChange: (Boolean) -> Unit,
showTitleUnreadCount: Boolean,
onShowTitleUnreadCountChange: (Boolean) -> Unit,
onStartActivity: (intent: Intent) -> Unit,
modifier: Modifier = Modifier,
) {
Expand Down Expand Up @@ -591,6 +597,12 @@ fun SettingsList(
onCheckedChange = onShowReadingTimeChange,
)

SwitchSetting(
title = stringResource(id = R.string.show_title_unread_count),
checked = showTitleUnreadCount,
onCheckedChange = onShowTitleUnreadCountChange,
)

HorizontalDivider(modifier = Modifier.width(dimens.maxContentWidth))

GroupTitle { innerModifier ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,10 @@ class SettingsViewModel(di: DI) : DIAwareViewModel(di) {
repository.setShowReadingTime(value)
}

fun setShowTitleUnreadCount(value: Boolean) {
repository.setShowTitleUnreadCount(value)
}

@OptIn(ExperimentalCoroutinesApi::class)
private val immutableFeedsSettings =
repository.feedNotificationSettings
Expand Down Expand Up @@ -199,6 +203,7 @@ class SettingsViewModel(di: DI) : DIAwareViewModel(di) {
repository.showOnlyTitle,
repository.isOpenAdjacent,
repository.showReadingTime,
repository.showTitleUnreadCount,
) { params: Array<Any> ->
@Suppress("UNCHECKED_CAST")
SettingsViewState(
Expand Down Expand Up @@ -228,6 +233,7 @@ class SettingsViewModel(di: DI) : DIAwareViewModel(di) {
showOnlyTitle = params[23] as Boolean,
isOpenAdjacent = params[24] as Boolean,
showReadingTime = params[25] as Boolean,
showTitleUnreadCount = params[26] as Boolean,
)
}.collect {
_viewState.value = it
Expand Down Expand Up @@ -269,6 +275,7 @@ data class SettingsViewState(
val showOnlyTitle: Boolean = false,
val isOpenAdjacent: Boolean = true,
val showReadingTime: Boolean = false,
val showTitleUnreadCount: Boolean = false,
)

data class UIFeedSettings(
Expand Down
3 changes: 2 additions & 1 deletion app/src/main/res/values-de/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -248,4 +248,5 @@
<string name="skip_duplicate_articles">Doppelte Artikel überspringen</string>
<string name="skip_duplicate_articles_desc">Artikel mit Links oder Titeln, die mit bestehenden Artikeln identisch sind, werden ignoriert</string>
<string name="feed_item_style_compact_card">Kompakte Kartei</string>
</resources>
<string name="show_title_unread_count">Zeige Anzahl ungelesener Artikel im Titel</string>
</resources>
4 changes: 3 additions & 1 deletion app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -261,5 +261,7 @@
<string name="close_menu">Close menu</string>
<string name="skip_duplicate_articles">Skip duplicate articles</string>
<string name="skip_duplicate_articles_desc">Articles with links or titles identical to existing articles are ignored</string>
<string name="touch_to_play_audio">Touch to play audio</string>
<string name="touch_to_play_audio">Touch to play audio</string>
<string name="title_unread_count" translatable="false">(%1$d)</string>
<string name="show_title_unread_count">Show unread article count in title</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ class RepositoryTest : DIAware {
every { settingsStore.syncOnlyOnWifi } returns MutableStateFlow(false)
every { settingsStore.addedFeederNews } returns MutableStateFlow(true)
every { settingsStore.minReadTime } returns MutableStateFlow(Instant.EPOCH)

every { feedItemStore.getFeedItemCountRaw(any(), any(), any(), any()) } returns flowOf(0)
}

@Test
Expand Down Expand Up @@ -286,7 +288,7 @@ class RepositoryTest : DIAware {
repository.getScreenTitleForFeedOrTag(ID_ALL_FEEDS, "").toList().first()
}

assertEquals(ScreenTitle(title = null, type = FeedType.ALL_FEEDS), result)
assertEquals(ScreenTitle(title = null, type = FeedType.ALL_FEEDS, unreadCount = 0), result)
}

@Test
Expand All @@ -296,7 +298,7 @@ class RepositoryTest : DIAware {
repository.getScreenTitleForFeedOrTag(ID_SAVED_ARTICLES, "").toList().first()
}

assertEquals(ScreenTitle(title = null, type = FeedType.SAVED_ARTICLES), result)
assertEquals(ScreenTitle(title = null, type = FeedType.SAVED_ARTICLES, unreadCount = 0), result)
}

@Test
Expand All @@ -306,7 +308,7 @@ class RepositoryTest : DIAware {
repository.getScreenTitleForFeedOrTag(ID_UNSET, "fwr").toList().first()
}

assertEquals(ScreenTitle(title = "fwr", type = FeedType.TAG), result)
assertEquals(ScreenTitle(title = "fwr", type = FeedType.TAG, unreadCount = 0), result)
}

@Test
Expand All @@ -318,7 +320,7 @@ class RepositoryTest : DIAware {
repository.getScreenTitleForFeedOrTag(5L, "fwr").toList().first()
}

assertEquals(ScreenTitle(title = "floppa", type = FeedType.FEED), result)
assertEquals(ScreenTitle(title = "floppa", type = FeedType.FEED, unreadCount = 0), result)

coVerify {
feedStore.getDisplayTitle(5L)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -390,4 +390,15 @@ class SettingsStoreTest : DIAware {
val allSettings = store.getAllSettings()
assertEquals(1, allSettings.size)
}

@Test
fun showTitleUnreadCount() {
store.setShowTitleUnreadCount(true)

verify {
sp.edit().putBoolean(PREF_SHOW_TITLE_UNREAD_COUNT, true).apply()
}

assertEquals(true, store.showTitleUnreadCount.value)
}
}
Loading