feat: show unread comments with different background in post detail (#286)

* add String to timestamp conversion utils

* refactor: key generation in sort serializer

* add long to long map serializer

* add post last seen date repository

* update login/logout use cases

* update post detail screen

* update DI
This commit is contained in:
Dieguitux 2025-01-22 23:42:04 +01:00 committed by GitHub
parent 65dea9c9a6
commit d5702e31d3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 362 additions and 13 deletions

View File

@ -0,0 +1,44 @@
package com.livefast.eattrash.raccoonforlemmy.core.persistence.repository
import kotlin.test.Test
import kotlin.test.assertEquals
class DefaultLongToLongMapSerializerTest {
private val sut = DefaultLongToLongMapSerializer()
@Test
fun givenEmpty_whenSerializeMap_thenResultIsAsExpected() {
val map = mapOf<Long, Long>()
val res = sut.serializeMap(map)
assertEquals(emptyList(), res)
}
@Test
fun whenSerializeMap_thenResultIsAsExpected() {
val map = mapOf(1L to 2L)
val res = sut.serializeMap(map)
assertEquals(listOf("1:2"), res)
}
@Test
fun givenEmpty_whenDeserializeMap_thenResultIsAsExpected() {
val list = listOf<String>()
val res = sut.deserializeMap(list)
assertEquals(mapOf(), res)
}
@Test
fun whenDeserializeMap_thenResultIsAsExpected() {
val list = listOf("1:2")
val res = sut.deserializeMap(list)
assertEquals(mapOf(1L to 2L), res)
}
}

View File

@ -0,0 +1,110 @@
package com.livefast.eattrash.raccoonforlemmy.core.persistence.repository
import com.livefast.eattrash.raccoonforlemmy.core.preferences.store.TemporaryKeyStore
import com.livefast.eattrash.raccoonforlemmy.core.testutils.DispatcherTestRule
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import junit.framework.TestCase.assertNull
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import kotlin.test.Test
import kotlin.test.assertEquals
class DefaultPostLastSeenDateRepositoryTest {
@get:Rule
val dispatcherTestRule = DispatcherTestRule()
private val keyStore = mockk<TemporaryKeyStore>(relaxUnitFun = true)
private val serializer =
mockk<LongToLongMapSerializer> {
every { deserializeMap(any()) } answers {
firstArg<List<String>>()
.associate { s ->
val tokens = s.split(":").map { it.trim() }
tokens[0].toLong() to tokens[1].toLong()
}.toMutableMap()
}
every { serializeMap(any()) } answers {
firstArg<Map<String, Int>>()
.map { entry ->
"${entry.key}:${entry.value}"
}.toList()
}
}
private val sut =
DefaultPostLastSeenDateRepository(
keyStore = keyStore,
serializer = serializer,
)
@Test
fun givenEmptyInitialState_whenSave_thenValueIsStored() =
runTest {
every { keyStore.get(KEY, listOf()) } returns listOf()
sut.save(1L, 2L)
verify {
keyStore.save(KEY, listOf("1:2"))
}
}
@Test
fun givenEntryAlreadyExisting_whenSave_thenValueIsStored() =
runTest {
every { keyStore.get(KEY, listOf()) } returns listOf("1:2")
sut.save(1, 3)
verify {
keyStore.save(KEY, listOf("1:3"))
}
}
@Test
fun givenOtherEntryAlreadyExisting_whenSave_thenBothValuesAreStored() =
runTest {
every { keyStore.get(KEY, listOf()) } returns listOf("1:2")
sut.save(3, 4)
verify {
keyStore.save(KEY, listOf("1:2", "3:4"))
}
}
@Test
fun givenEmptyInitialState_whenGet_thenResultIsAsExpected() =
runTest {
every { keyStore.get(KEY, listOf()) } returns listOf()
val res = sut.get(1L)
assertNull(res)
}
@Test
fun givenCommunityExists_whenGet_thenResultIsAsExpected() =
runTest {
every { keyStore.get(KEY, listOf()) } returns listOf("1:2")
val res = sut.get(1L)
assertEquals(2, res)
}
@Test
fun givenCommunityDoesNotExist_whenGet_thenResultIsAsExpected() =
runTest {
every { keyStore.get(KEY, listOf()) } returns listOf("1:2")
val res = sut.get(3L)
assertNull(res)
}
companion object {
private const val KEY = "postLastSeenDate"
}
}

View File

@ -12,7 +12,9 @@ import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.Default
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.DefaultDraftRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.DefaultFavoriteCommunityRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.DefaultInstanceSelectionRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.DefaultLongToLongMapSerializer
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.DefaultMultiCommunityRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.DefaultPostLastSeenDateRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.DefaultSettingsRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.DefaultSortSerializer
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.DefaultStopWordRepository
@ -22,7 +24,9 @@ import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.DomainB
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.DraftRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.FavoriteCommunityRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.InstanceSelectionRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.LongToLongMapSerializer
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.MultiCommunityRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.PostLastSeenDateRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.SettingsRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.SortSerializer
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.StopWordRepository
@ -164,4 +168,17 @@ val persistenceModule =
)
}
}
bind<LongToLongMapSerializer> {
singleton {
DefaultLongToLongMapSerializer()
}
}
bind<PostLastSeenDateRepository> {
singleton {
DefaultPostLastSeenDateRepository(
keyStore = instance(),
serializer = instance(),
)
}
}
}

View File

@ -0,0 +1,25 @@
package com.livefast.eattrash.raccoonforlemmy.core.persistence.repository
internal class DefaultLongToLongMapSerializer : LongToLongMapSerializer {
override fun deserializeMap(list: List<String>): MutableMap<Long, Long> =
list
.mapNotNull {
it.split(":").takeIf { e -> e.size == 2 }?.let { e -> e[0] to e[1] }
}.let { pairs ->
val res = mutableMapOf<Long, Long>()
for (pair in pairs) {
res[pair.first.toLong()] = pair.second.toLong()
}
res
}
override fun serializeMap(map: Map<Long, Long>): List<String> =
map.map { e ->
buildString {
append("")
append(e.key)
append(":")
append(e.value)
}
}
}

View File

@ -0,0 +1,42 @@
package com.livefast.eattrash.raccoonforlemmy.core.persistence.repository
import com.livefast.eattrash.raccoonforlemmy.core.preferences.store.TemporaryKeyStore
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
import kotlinx.coroutines.withContext
internal class DefaultPostLastSeenDateRepository(
private val keyStore: TemporaryKeyStore,
private val serializer: LongToLongMapSerializer,
) : PostLastSeenDateRepository {
override suspend fun get(postId: Long): Long? =
withContext(Dispatchers.IO) {
val map =
keyStore.get(SETTINGS_KEY, listOf()).let {
serializer.deserializeMap(it)
}
map[postId]
}
override suspend fun save(
postId: Long,
timestamp: Long,
) = withContext(Dispatchers.IO) {
val map =
keyStore.get(SETTINGS_KEY, listOf()).let {
serializer.deserializeMap(it)
}
map[postId] = timestamp
val newValue = serializer.serializeMap(map)
keyStore.save(SETTINGS_KEY, newValue)
}
override suspend fun clear() =
withContext(Dispatchers.IO) {
keyStore.remove(SETTINGS_KEY)
}
companion object {
private const val SETTINGS_KEY = "postLastSeenDate"
}
}

View File

@ -15,6 +15,10 @@ internal class DefaultSortSerializer : SortSerializer {
override fun serializeMap(map: Map<String, Int>): List<String> =
map.map { e ->
e.key + ":" + e.value
buildString {
append(e.key)
append(":")
append(e.value)
}
}
}

View File

@ -0,0 +1,7 @@
package com.livefast.eattrash.raccoonforlemmy.core.persistence.repository
internal interface LongToLongMapSerializer {
fun deserializeMap(list: List<String>): MutableMap<Long, Long>
fun serializeMap(map: Map<Long, Long>): List<String>
}

View File

@ -0,0 +1,12 @@
package com.livefast.eattrash.raccoonforlemmy.core.persistence.repository
interface PostLastSeenDateRepository {
suspend fun get(postId: Long): Long?
suspend fun save(
postId: Long,
timestamp: Long,
)
suspend fun clear()
}

View File

@ -25,6 +25,11 @@ actual fun Long.toIso8601Timestamp(): String? {
return safeFormatter.format(date)
}
actual fun String.toTimestamp(): Long {
val date = getDateFromIso8601Timestamp(this)
return date.toInstant().toEpochMilli()
}
actual fun getFormattedDate(
iso8601Timestamp: String,
format: String,

View File

@ -4,6 +4,8 @@ expect fun epochMillis(): Long
expect fun Long.toIso8601Timestamp(): String?
expect fun String.toTimestamp(): Long
expect fun getFormattedDate(
iso8601Timestamp: String,
format: String,

View File

@ -16,6 +16,7 @@ import platform.Foundation.NSTimeZone
import platform.Foundation.autoupdatingCurrentLocale
import platform.Foundation.localTimeZone
import platform.Foundation.timeIntervalSince1970
import kotlin.math.roundToLong
actual fun epochMillis(): Long = (NSDate().timeIntervalSince1970 * 1000).toLong()
@ -28,6 +29,11 @@ actual fun Long.toIso8601Timestamp(): String? {
return dateFormatter.stringFromDate(date)
}
actual fun String.toTimestamp(): Long {
val date = getDateFromIso8601Timestamp(this)
return date?.timeIntervalSince1970?.let { (it * 1000).roundToLong() } ?: 0
}
actual fun getFormattedDate(
iso8601Timestamp: String,
format: String,

View File

@ -7,7 +7,9 @@ import com.livefast.eattrash.raccoonforlemmy.core.persistence.data.SettingsModel
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.AccountRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.CommunityPreferredLanguageRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.CommunitySortRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.PostLastSeenDateRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.SettingsRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.UserSortRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.usecase.CreateSpecialTagsUseCase
import com.livefast.eattrash.raccoonforlemmy.core.testutils.DispatcherTestRule
import com.livefast.eattrash.raccoonforlemmy.domain.identity.repository.ApiConfigurationRepository
@ -42,6 +44,8 @@ class DefaultLoginUseCaseTest {
private val communitySortRepository = mockk<CommunitySortRepository>(relaxUnitFun = true)
private val communityPreferredLanguageRepository =
mockk<CommunityPreferredLanguageRepository>(relaxUnitFun = true)
private val userSortRepository = mockk<UserSortRepository>(relaxUnitFun = true)
private val postLastSeenDateRepository = mockk<PostLastSeenDateRepository>(relaxUnitFun = true)
private val bottomNavItemsRepository =
mockk<BottomNavItemsRepository>(relaxUnitFun = true) {
coEvery { get(accountId = any()) } returns BottomNavItemsRepository.DEFAULT_ITEMS
@ -61,6 +65,8 @@ class DefaultLoginUseCaseTest {
bottomNavItemsRepository = bottomNavItemsRepository,
lemmyValueCache = lemmyValueCache,
createSpecialTagsUseCase = createSpecialTagsUseCase,
userSortRepository = userSortRepository,
postLastSeenDateRepository = postLastSeenDateRepository,
)
@Test
@ -104,6 +110,8 @@ class DefaultLoginUseCaseTest {
settingsRepository.createSettings(anonymousSettings, accountId)
settingsRepository.changeCurrentSettings(anonymousSettings)
communitySortRepository.clear()
userSortRepository.clear()
postLastSeenDateRepository.clear()
}
}
@ -156,6 +164,8 @@ class DefaultLoginUseCaseTest {
settingsRepository.createSettings(anonymousSettings, accountId)
settingsRepository.changeCurrentSettings(anonymousSettings)
communitySortRepository.clear()
userSortRepository.clear()
postLastSeenDateRepository.clear()
}
}
@ -201,6 +211,8 @@ class DefaultLoginUseCaseTest {
settingsRepository.changeCurrentSettings(oldSettings)
communitySortRepository.clear()
communityPreferredLanguageRepository.clear()
userSortRepository.clear()
postLastSeenDateRepository.clear()
}
coVerify(inverse = true) {
accountRepository.createAccount(any())

View File

@ -7,7 +7,9 @@ import com.livefast.eattrash.raccoonforlemmy.core.persistence.data.AccountModel
import com.livefast.eattrash.raccoonforlemmy.core.persistence.data.SettingsModel
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.AccountRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.CommunitySortRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.PostLastSeenDateRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.SettingsRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.UserSortRepository
import com.livefast.eattrash.raccoonforlemmy.core.testutils.DispatcherTestRule
import com.livefast.eattrash.raccoonforlemmy.domain.identity.repository.IdentityRepository
import com.livefast.eattrash.raccoonforlemmy.domain.lemmy.repository.LemmyValueCache
@ -29,6 +31,8 @@ class DefaultLogoutUseCaseTest {
private val notificationCenter = mockk<NotificationCenter>(relaxUnitFun = true)
private val communitySortRepository = mockk<CommunitySortRepository>(relaxUnitFun = true)
private val lemmyValueCache = mockk<LemmyValueCache>(relaxUnitFun = true)
private val userSortRepository = mockk<UserSortRepository>(relaxUnitFun = true)
private val postLastSeenDateRepository = mockk<PostLastSeenDateRepository>(relaxUnitFun = true)
private val bottomNavItemsRepository =
mockk<BottomNavItemsRepository>(relaxUnitFun = true) {
coEvery { get(accountId = any()) } returns BottomNavItemsRepository.DEFAULT_ITEMS
@ -44,6 +48,8 @@ class DefaultLogoutUseCaseTest {
bottomNavItemsRepository = bottomNavItemsRepository,
lemmyValueCache = lemmyValueCache,
userTagHelper = userTagHelper,
userSortRepository = userSortRepository,
postLastSeenDateRepository = postLastSeenDateRepository,
)
@Test
@ -74,6 +80,8 @@ class DefaultLogoutUseCaseTest {
accountRepository.setActive(accountId, false)
settingsRepository.changeCurrentSettings(anonymousSettings)
userTagHelper.clear()
userSortRepository.clear()
postLastSeenDateRepository.clear()
}
}
}

View File

@ -9,7 +9,9 @@ import com.livefast.eattrash.raccoonforlemmy.core.persistence.data.SettingsModel
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.AccountRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.CommunityPreferredLanguageRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.CommunitySortRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.PostLastSeenDateRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.SettingsRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.UserSortRepository
import com.livefast.eattrash.raccoonforlemmy.core.testutils.DispatcherTestRule
import com.livefast.eattrash.raccoonforlemmy.domain.identity.repository.IdentityRepository
import com.livefast.eattrash.raccoonforlemmy.domain.lemmy.repository.LemmyValueCache
@ -33,6 +35,8 @@ class DefaultSwitchAccountUseCaseTest {
private val communitySortRepository = mockk<CommunitySortRepository>(relaxUnitFun = true)
private val communityPreferredLanguageRepository =
mockk<CommunityPreferredLanguageRepository>(relaxUnitFun = true)
private val userSortRepository = mockk<UserSortRepository>(relaxUnitFun = true)
private val postLastSeenDateRepository = mockk<PostLastSeenDateRepository>(relaxUnitFun = true)
private val lemmyValueCache = mockk<LemmyValueCache>(relaxUnitFun = true)
private val bottomNavItemsRepository =
mockk<BottomNavItemsRepository>(relaxUnitFun = true) {
@ -51,6 +55,8 @@ class DefaultSwitchAccountUseCaseTest {
bottomNavItemsRepository = bottomNavItemsRepository,
lemmyValueCache = lemmyValueCache,
userTagHelper = userTagHelper,
userSortRepository = userSortRepository,
postLastSeenDateRepository = postLastSeenDateRepository,
)
@Test
@ -101,6 +107,8 @@ class DefaultSwitchAccountUseCaseTest {
identityRepository.refreshLoggedState()
serviceProvider.changeInstance("new-instance")
userTagHelper.clear()
userSortRepository.clear()
postLastSeenDateRepository.clear()
}
}
}

View File

@ -139,6 +139,8 @@ val identityModule =
bottomNavItemsRepository = instance(),
lemmyValueCache = instance(),
createSpecialTagsUseCase = instance(),
userSortRepository = instance(),
postLastSeenDateRepository = instance(),
)
}
}
@ -153,6 +155,8 @@ val identityModule =
bottomNavItemsRepository = instance(),
userTagHelper = instance(),
lemmyValueCache = instance(),
userSortRepository = instance(),
postLastSeenDateRepository = instance(),
)
}
}
@ -169,6 +173,8 @@ val identityModule =
bottomNavItemsRepository = instance(),
userTagHelper = instance(),
lemmyValueCache = instance(),
userSortRepository = instance(),
postLastSeenDateRepository = instance(),
)
}
}

View File

@ -7,7 +7,9 @@ import com.livefast.eattrash.raccoonforlemmy.core.persistence.data.AccountModel
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.AccountRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.CommunityPreferredLanguageRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.CommunitySortRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.PostLastSeenDateRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.SettingsRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.UserSortRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.usecase.CreateSpecialTagsUseCase
import com.livefast.eattrash.raccoonforlemmy.domain.identity.repository.ApiConfigurationRepository
import com.livefast.eattrash.raccoonforlemmy.domain.identity.repository.AuthRepository
@ -27,6 +29,8 @@ internal class DefaultLoginUseCase(
private val bottomNavItemsRepository: BottomNavItemsRepository,
private val lemmyValueCache: LemmyValueCache,
private val createSpecialTagsUseCase: CreateSpecialTagsUseCase,
private val userSortRepository: UserSortRepository,
private val postLastSeenDateRepository: PostLastSeenDateRepository,
) : LoginUseCase {
override suspend operator fun invoke(
instance: String,
@ -108,6 +112,8 @@ internal class DefaultLoginUseCase(
communitySortRepository.clear()
communityPreferredLanguageRepository.clear()
userSortRepository.clear()
postLastSeenDateRepository.clear()
val newSettings = settingsRepository.getSettings(accountId)
settingsRepository.changeCurrentSettings(newSettings)

View File

@ -6,7 +6,9 @@ import com.livefast.eattrash.raccoonforlemmy.core.notifications.NotificationCent
import com.livefast.eattrash.raccoonforlemmy.core.notifications.NotificationCenterEvent
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.AccountRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.CommunitySortRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.PostLastSeenDateRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.SettingsRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.UserSortRepository
import com.livefast.eattrash.raccoonforlemmy.domain.identity.repository.IdentityRepository
import com.livefast.eattrash.raccoonforlemmy.domain.lemmy.repository.LemmyValueCache
import com.livefast.eattrash.raccoonforlemmy.domain.lemmy.repository.UserTagHelper
@ -20,6 +22,8 @@ internal class DefaultLogoutUseCase(
private val bottomNavItemsRepository: BottomNavItemsRepository,
private val lemmyValueCache: LemmyValueCache,
private val userTagHelper: UserTagHelper,
private val userSortRepository: UserSortRepository,
private val postLastSeenDateRepository: PostLastSeenDateRepository,
) : LogoutUseCase {
override suspend operator fun invoke() {
notificationCenter.send(NotificationCenterEvent.ResetExplore)
@ -27,6 +31,8 @@ internal class DefaultLogoutUseCase(
identityRepository.clearToken()
communitySortRepository.clear()
userSortRepository.clear()
postLastSeenDateRepository.clear()
lemmyValueCache.refresh()
notificationCenter.send(NotificationCenterEvent.Logout)

View File

@ -9,7 +9,9 @@ import com.livefast.eattrash.raccoonforlemmy.core.persistence.data.AccountModel
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.AccountRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.CommunityPreferredLanguageRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.CommunitySortRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.PostLastSeenDateRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.SettingsRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.UserSortRepository
import com.livefast.eattrash.raccoonforlemmy.domain.identity.repository.IdentityRepository
import com.livefast.eattrash.raccoonforlemmy.domain.lemmy.repository.LemmyValueCache
import com.livefast.eattrash.raccoonforlemmy.domain.lemmy.repository.UserTagHelper
@ -25,6 +27,8 @@ internal class DefaultSwitchAccountUseCase(
private val bottomNavItemsRepository: BottomNavItemsRepository,
private val lemmyValueCache: LemmyValueCache,
private val userTagHelper: UserTagHelper,
private val userSortRepository: UserSortRepository,
private val postLastSeenDateRepository: PostLastSeenDateRepository,
) : SwitchAccountUseCase {
override suspend fun invoke(account: AccountModel) {
val accountId = account.id ?: return
@ -39,6 +43,8 @@ internal class DefaultSwitchAccountUseCase(
notificationCenter.send(NotificationCenterEvent.Logout)
communitySortRepository.clear()
userSortRepository.clear()
postLastSeenDateRepository.clear()
communityPreferredLanguageRepository.clear()
serviceProvider.changeInstance(instance)

View File

@ -150,6 +150,7 @@ interface PostDetailMviModel :
val meTagColor: Int? = null,
val modTagColor: Int? = null,
val opTagColor: Int? = null,
val lastSeenTimestamp: Long? = null,
)
sealed interface Effect {

View File

@ -120,6 +120,7 @@ import com.livefast.eattrash.raccoonforlemmy.core.persistence.data.ActionOnSwipe
import com.livefast.eattrash.raccoonforlemmy.core.persistence.di.getSettingsRepository
import com.livefast.eattrash.raccoonforlemmy.core.utils.VoteAction
import com.livefast.eattrash.raccoonforlemmy.core.utils.compose.onClick
import com.livefast.eattrash.raccoonforlemmy.core.utils.datetime.toTimestamp
import com.livefast.eattrash.raccoonforlemmy.core.utils.toIcon
import com.livefast.eattrash.raccoonforlemmy.core.utils.toLocalDp
import com.livefast.eattrash.raccoonforlemmy.core.utils.toModifier
@ -1063,22 +1064,33 @@ class PostDetailScreen(
emptyList()
},
content = {
val commentTs =
with(comment) {
updateDate ?: publishDate
}?.toTimestamp()
val lastSeenTs = uiState.lastSeenTimestamp
val isAfterLastSeenTs = commentTs != null &&
lastSeenTs != null &&
commentTs > lastSeenTs
val backgroundModifier =
when {
commentIdToHighlight == comment.id ||
(commentIdToHighlight == null && isAfterLastSeenTs)
->
Modifier.background(
MaterialTheme.colorScheme
.surfaceColorAtElevation(
5.dp,
).copy(alpha = 0.75f),
)
else -> Modifier
}
CommentCard(
modifier =
Modifier
.background(MaterialTheme.colorScheme.background)
.then(
if (comment.id == commentIdToHighlight) {
Modifier.background(
MaterialTheme.colorScheme
.surfaceColorAtElevation(
5.dp,
).copy(alpha = 0.75f),
)
} else {
Modifier
},
),
.then(backgroundModifier),
comment = comment,
isOp = comment.creator?.id == uiState.post.creator?.id,
showBot = true,

View File

@ -7,8 +7,10 @@ import com.livefast.eattrash.raccoonforlemmy.core.notifications.NotificationCent
import com.livefast.eattrash.raccoonforlemmy.core.notifications.NotificationCenterEvent
import com.livefast.eattrash.raccoonforlemmy.core.persistence.data.UserTagType
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.AccountRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.PostLastSeenDateRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.SettingsRepository
import com.livefast.eattrash.raccoonforlemmy.core.persistence.repository.UserTagRepository
import com.livefast.eattrash.raccoonforlemmy.core.utils.datetime.epochMillis
import com.livefast.eattrash.raccoonforlemmy.core.utils.share.ShareHelper
import com.livefast.eattrash.raccoonforlemmy.core.utils.vibrate.HapticFeedback
import com.livefast.eattrash.raccoonforlemmy.domain.identity.repository.ApiConfigurationRepository
@ -60,6 +62,7 @@ class PostDetailViewModel(
private val settingsRepository: SettingsRepository,
private val accountRepository: AccountRepository,
private val userTagRepository: UserTagRepository,
private val postLastSeenDateRepository: PostLastSeenDateRepository,
private val userTagHelper: UserTagHelper,
private val shareHelper: ShareHelper,
private val notificationCenter: NotificationCenter,
@ -95,14 +98,20 @@ class PostDetailViewModel(
}
if (uiState.value.post.id == 0L) {
val post = itemCache.getPost(postId) ?: PostModel()
val lastSeenTimestamp = postLastSeenDateRepository.get(postId)
updateState {
it.copy(
post = post,
isModerator = isModerator,
currentUserId = identityRepository.cachedUser?.id,
canFetchMore = it.comments.size < post.comments,
lastSeenTimestamp = lastSeenTimestamp,
)
}
if (identityRepository.isLogged.value == true) {
val now = epochMillis()
postLastSeenDateRepository.save(postId = postId, timestamp = now)
}
}
themeRepository.postLayout

View File

@ -35,6 +35,7 @@ val postDetailModule =
accountRepository = instance(),
userTagRepository = instance(),
userTagHelper = instance(),
postLastSeenDateRepository = instance(),
shareHelper = instance(),
notificationCenter = instance(),
hapticFeedback = instance(),