fix: store expiration timestamps in viewedPollResultNoteIds to prevent unbounded growth
Changes viewedPollResultNoteIds from Set<String> to Map<String, Long> where the value is the expiration timestamp. Uses the poll's endsAt date if set and in the future, otherwise now + 24 hours. Expired entries are pruned on each new insertion. Serialized as JSON instead of StringSet. https://claude.ai/code/session_01EkUYT4giQPUvbAJZ54o1se
This commit is contained in:
@@ -392,7 +392,10 @@ object LocalPreferences {
|
|||||||
)
|
)
|
||||||
putStringSet(PrefKeys.HAS_DONATED_IN_VERSION, settings.hasDonatedInVersion.value)
|
putStringSet(PrefKeys.HAS_DONATED_IN_VERSION, settings.hasDonatedInVersion.value)
|
||||||
putStringSet(PrefKeys.DISMISSED_POLL_NOTE_IDS, settings.dismissedPollNoteIds.value)
|
putStringSet(PrefKeys.DISMISSED_POLL_NOTE_IDS, settings.dismissedPollNoteIds.value)
|
||||||
putStringSet(PrefKeys.VIEWED_POLL_RESULT_NOTE_IDS, settings.viewedPollResultNoteIds.value)
|
putString(
|
||||||
|
PrefKeys.VIEWED_POLL_RESULT_NOTE_IDS,
|
||||||
|
JsonMapper.toJson(settings.viewedPollResultNoteIds.value),
|
||||||
|
)
|
||||||
|
|
||||||
putString(
|
putString(
|
||||||
PrefKeys.PENDING_ATTESTATIONS,
|
PrefKeys.PENDING_ATTESTATIONS,
|
||||||
@@ -478,7 +481,7 @@ object LocalPreferences {
|
|||||||
val hideNIP17WarningDialog = getBoolean(PrefKeys.HIDE_NIP_17_WARNING_DIALOG, false)
|
val hideNIP17WarningDialog = getBoolean(PrefKeys.HIDE_NIP_17_WARNING_DIALOG, false)
|
||||||
val hasDonatedInVersion = getStringSet(PrefKeys.HAS_DONATED_IN_VERSION, null) ?: setOf()
|
val hasDonatedInVersion = getStringSet(PrefKeys.HAS_DONATED_IN_VERSION, null) ?: setOf()
|
||||||
val dismissedPollNoteIds = getStringSet(PrefKeys.DISMISSED_POLL_NOTE_IDS, null) ?: setOf()
|
val dismissedPollNoteIds = getStringSet(PrefKeys.DISMISSED_POLL_NOTE_IDS, null) ?: setOf()
|
||||||
val viewedPollResultNoteIds = getStringSet(PrefKeys.VIEWED_POLL_RESULT_NOTE_IDS, null) ?: setOf()
|
val viewedPollResultNoteIdsStr = getString(PrefKeys.VIEWED_POLL_RESULT_NOTE_IDS, null)
|
||||||
val localRelayServers = getStringSet(PrefKeys.LOCAL_RELAY_SERVERS, null) ?: setOf()
|
val localRelayServers = getStringSet(PrefKeys.LOCAL_RELAY_SERVERS, null) ?: setOf()
|
||||||
|
|
||||||
val defaultHomeFollowListStr = getString(PrefKeys.DEFAULT_HOME_FOLLOW_LIST, null)
|
val defaultHomeFollowListStr = getString(PrefKeys.DEFAULT_HOME_FOLLOW_LIST, null)
|
||||||
@@ -531,6 +534,7 @@ object LocalPreferences {
|
|||||||
val zapPaymentRequestServer = async { parseOrNull<Nip47WalletConnect.Nip47URI>(zapPaymentRequestServerStr) }
|
val zapPaymentRequestServer = async { parseOrNull<Nip47WalletConnect.Nip47URI>(zapPaymentRequestServerStr) }
|
||||||
val defaultFileServer = async { parseOrNull<ServerName>(defaultFileServerStr) ?: DEFAULT_MEDIA_SERVERS[0] }
|
val defaultFileServer = async { parseOrNull<ServerName>(defaultFileServerStr) ?: DEFAULT_MEDIA_SERVERS[0] }
|
||||||
|
|
||||||
|
val viewedPollResultNoteIds = async { parseOrNull<Map<String, Long>>(viewedPollResultNoteIdsStr) ?: mapOf() }
|
||||||
val pendingAttestations = async { parseOrNull<Map<HexKey, String>>(pendingAttestationsStr) ?: mapOf() }
|
val pendingAttestations = async { parseOrNull<Map<HexKey, String>>(pendingAttestationsStr) ?: mapOf() }
|
||||||
val latestUserMetadata = async { parseEventOrNull<MetadataEvent>(latestUserMetadataStr) }
|
val latestUserMetadata = async { parseEventOrNull<MetadataEvent>(latestUserMetadataStr) }
|
||||||
val latestContactList = async { parseEventOrNull<ContactListEvent>(latestContactListStr) }
|
val latestContactList = async { parseEventOrNull<ContactListEvent>(latestContactListStr) }
|
||||||
@@ -601,7 +605,7 @@ object LocalPreferences {
|
|||||||
lastReadPerRoute = MutableStateFlow(lastReadPerRoute.await()),
|
lastReadPerRoute = MutableStateFlow(lastReadPerRoute.await()),
|
||||||
hasDonatedInVersion = MutableStateFlow(hasDonatedInVersion),
|
hasDonatedInVersion = MutableStateFlow(hasDonatedInVersion),
|
||||||
dismissedPollNoteIds = MutableStateFlow(dismissedPollNoteIds),
|
dismissedPollNoteIds = MutableStateFlow(dismissedPollNoteIds),
|
||||||
viewedPollResultNoteIds = MutableStateFlow(viewedPollResultNoteIds),
|
viewedPollResultNoteIds = MutableStateFlow(viewedPollResultNoteIds.await()),
|
||||||
pendingAttestations = MutableStateFlow(pendingAttestations.await()),
|
pendingAttestations = MutableStateFlow(pendingAttestations.await()),
|
||||||
backupNipA3PaymentTargets = latestPaymentTargets.await(),
|
backupNipA3PaymentTargets = latestPaymentTargets.await(),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2154,7 +2154,10 @@ class Account(
|
|||||||
|
|
||||||
fun hasViewedPollResults(noteId: String) = settings.hasViewedPollResults(noteId)
|
fun hasViewedPollResults(noteId: String) = settings.hasViewedPollResults(noteId)
|
||||||
|
|
||||||
fun markPollResultsViewed(noteId: String) = settings.markPollResultsViewed(noteId)
|
fun markPollResultsViewed(
|
||||||
|
noteId: String,
|
||||||
|
pollEndsAt: Long?,
|
||||||
|
) = settings.markPollResultsViewed(noteId, pollEndsAt)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
Log.d("AccountRegisterObservers", "Init")
|
Log.d("AccountRegisterObservers", "Init")
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ import com.vitorpamplona.quartz.nip65RelayList.tags.AdvertisedRelayType
|
|||||||
import com.vitorpamplona.quartz.nip72ModCommunities.follow.CommunityListEvent
|
import com.vitorpamplona.quartz.nip72ModCommunities.follow.CommunityListEvent
|
||||||
import com.vitorpamplona.quartz.nip78AppData.AppSpecificDataEvent
|
import com.vitorpamplona.quartz.nip78AppData.AppSpecificDataEvent
|
||||||
import com.vitorpamplona.quartz.nip85TrustedAssertions.list.TrustProviderListEvent
|
import com.vitorpamplona.quartz.nip85TrustedAssertions.list.TrustProviderListEvent
|
||||||
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
@@ -194,7 +195,7 @@ class AccountSettings(
|
|||||||
val lastReadPerRoute: MutableStateFlow<Map<String, MutableStateFlow<Long>>> = MutableStateFlow(mapOf()),
|
val lastReadPerRoute: MutableStateFlow<Map<String, MutableStateFlow<Long>>> = MutableStateFlow(mapOf()),
|
||||||
val hasDonatedInVersion: MutableStateFlow<Set<String>> = MutableStateFlow(setOf()),
|
val hasDonatedInVersion: MutableStateFlow<Set<String>> = MutableStateFlow(setOf()),
|
||||||
val dismissedPollNoteIds: MutableStateFlow<Set<String>> = MutableStateFlow(setOf()),
|
val dismissedPollNoteIds: MutableStateFlow<Set<String>> = MutableStateFlow(setOf()),
|
||||||
val viewedPollResultNoteIds: MutableStateFlow<Set<String>> = MutableStateFlow(setOf()),
|
val viewedPollResultNoteIds: MutableStateFlow<Map<String, Long>> = MutableStateFlow(mapOf()),
|
||||||
val pendingAttestations: MutableStateFlow<Map<HexKey, String>> = MutableStateFlow(mapOf()),
|
val pendingAttestations: MutableStateFlow<Map<HexKey, String>> = MutableStateFlow(mapOf()),
|
||||||
var backupNipA3PaymentTargets: PaymentTargetsEvent? = null,
|
var backupNipA3PaymentTargets: PaymentTargetsEvent? = null,
|
||||||
) : EphemeralChatRepository,
|
) : EphemeralChatRepository,
|
||||||
@@ -700,17 +701,34 @@ class AccountSettings(
|
|||||||
// viewed poll results
|
// viewed poll results
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
fun hasViewedPollResults(noteId: String) = viewedPollResultNoteIds.value.contains(noteId)
|
fun hasViewedPollResults(noteId: String): Boolean {
|
||||||
|
val expiresAt = viewedPollResultNoteIds.value[noteId] ?: return false
|
||||||
|
return expiresAt > TimeUtils.now()
|
||||||
|
}
|
||||||
|
|
||||||
fun markPollResultsViewed(noteId: String) {
|
fun markPollResultsViewed(
|
||||||
if (!viewedPollResultNoteIds.value.contains(noteId)) {
|
noteId: String,
|
||||||
|
pollEndsAt: Long?,
|
||||||
|
) {
|
||||||
|
if (noteId !in viewedPollResultNoteIds.value) {
|
||||||
|
val expiresAt =
|
||||||
|
if (pollEndsAt != null && pollEndsAt > TimeUtils.now()) {
|
||||||
|
pollEndsAt
|
||||||
|
} else {
|
||||||
|
TimeUtils.now() + TimeUtils.ONE_DAY
|
||||||
|
}
|
||||||
viewedPollResultNoteIds.update {
|
viewedPollResultNoteIds.update {
|
||||||
it + noteId
|
pruneExpiredViews(it) + (noteId to expiresAt)
|
||||||
}
|
}
|
||||||
saveAccountSettings()
|
saveAccountSettings()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun pruneExpiredViews(views: Map<String, Long>): Map<String, Long> {
|
||||||
|
val now = TimeUtils.now()
|
||||||
|
return views.filterValues { it > now }
|
||||||
|
}
|
||||||
|
|
||||||
// ----
|
// ----
|
||||||
// last read flows
|
// last read flows
|
||||||
// ----
|
// ----
|
||||||
|
|||||||
@@ -271,7 +271,7 @@ fun RenderPollCard(
|
|||||||
accountViewModel.account.pollRespond(event, responses)
|
accountViewModel.account.pollRespond(event, responses)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onViewResults = { accountViewModel.markPollResultsViewed(event.id) },
|
onViewResults = { accountViewModel.markPollResultsViewed(event.id, event.endsAt()) },
|
||||||
hasViewedResults = { accountViewModel.hasViewedPollResults(event.id) },
|
hasViewedResults = { accountViewModel.hasViewedPollResults(event.id) },
|
||||||
resultContent = galleryUser,
|
resultContent = galleryUser,
|
||||||
labelContent = labelContent,
|
labelContent = labelContent,
|
||||||
|
|||||||
+4
-1
@@ -1130,7 +1130,10 @@ class AccountViewModel(
|
|||||||
|
|
||||||
fun hasViewedPollResults(noteId: String) = account.hasViewedPollResults(noteId)
|
fun hasViewedPollResults(noteId: String) = account.hasViewedPollResults(noteId)
|
||||||
|
|
||||||
fun markPollResultsViewed(noteId: String) = account.markPollResultsViewed(noteId)
|
fun markPollResultsViewed(
|
||||||
|
noteId: String,
|
||||||
|
pollEndsAt: Long?,
|
||||||
|
) = account.markPollResultsViewed(noteId, pollEndsAt)
|
||||||
|
|
||||||
fun dontTranslateFrom() = account.settings.syncedSettings.languages.dontTranslateFrom.value
|
fun dontTranslateFrom() = account.settings.syncedSettings.languages.dontTranslateFrom.value
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user