Skip to content

Commit

Permalink
Added option to show unread count in title (#300)
Browse files Browse the repository at this point in the history
* feat: show unread count in title bar

* chore: fix formatting

* fix: tests
  • Loading branch information
svenjacobs committed Jun 10, 2024
1 parent f23390e commit a42529e
Show file tree
Hide file tree
Showing 10 changed files with 111 additions and 44 deletions.
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)
}
}

0 comments on commit a42529e

Please sign in to comment.