Bind account early in timeline & notification view models

This will make it less likely that data from different accounts will
get mixed up
This commit is contained in:
ivk 2024-07-13 18:18:53 +02:00 committed by charlag
parent bd09b197f0
commit 594204bdfd
No known key found for this signature in database
GPG Key ID: 5B96E7C76F0CA558
2 changed files with 55 additions and 67 deletions

View File

@ -46,7 +46,6 @@ import com.keylesspalace.tusky.network.FilterModel
import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.settings.PrefKeys import com.keylesspalace.tusky.settings.PrefKeys
import com.keylesspalace.tusky.usecase.TimelineCases import com.keylesspalace.tusky.usecase.TimelineCases
import com.keylesspalace.tusky.util.EmptyPagingSource
import com.keylesspalace.tusky.util.deserialize import com.keylesspalace.tusky.util.deserialize
import com.keylesspalace.tusky.util.serialize import com.keylesspalace.tusky.util.serialize
import com.keylesspalace.tusky.viewdata.NotificationViewData import com.keylesspalace.tusky.viewdata.NotificationViewData
@ -87,6 +86,8 @@ class NotificationsViewModel @Inject constructor(
/** Map from notification id to translation. */ /** Map from notification id to translation. */
private val translations = MutableStateFlow(mapOf<String, TranslationViewData>()) private val translations = MutableStateFlow(mapOf<String, TranslationViewData>())
private val account = accountManager.activeAccount!!
private var remoteMediator = NotificationsRemoteMediator(accountManager, api, db, excludes.value) private var remoteMediator = NotificationsRemoteMediator(accountManager, api, db, excludes.value)
private var readingOrder: ReadingOrder = private var readingOrder: ReadingOrder =
@ -100,12 +101,7 @@ class NotificationsViewModel @Inject constructor(
), ),
remoteMediator = remoteMediator, remoteMediator = remoteMediator,
pagingSourceFactory = { pagingSourceFactory = {
val activeAccount = accountManager.activeAccount db.notificationsDao().getNotifications(account.id)
if (activeAccount == null) {
EmptyPagingSource()
} else {
db.notificationsDao().getNotifications(activeAccount.id)
}
} }
).flow ).flow
.cachedIn(viewModelScope) .cachedIn(viewModelScope)
@ -142,16 +138,13 @@ class NotificationsViewModel @Inject constructor(
fun updateNotificationFilters(newFilters: Set<Notification.Type>) { fun updateNotificationFilters(newFilters: Set<Notification.Type>) {
if (newFilters != _excludes.value) { if (newFilters != _excludes.value) {
val account = accountManager.activeAccount viewModelScope.launch {
if (account != null) { account.notificationsFilter = serialize(newFilters)
viewModelScope.launch { accountManager.saveAccount(account)
account.notificationsFilter = serialize(newFilters) remoteMediator.excludes = newFilters
accountManager.saveAccount(account) db.notificationsDao().cleanupNotifications(account.id, 0)
remoteMediator.excludes = newFilters refreshTrigger.value++
db.notificationsDao().cleanupNotifications(account.id, 0) _excludes.value = newFilters
refreshTrigger.value++
_excludes.value = newFilters
}
} }
} }
} }
@ -165,6 +158,7 @@ class NotificationsViewModel @Inject constructor(
} }
Filter.Action.NONE Filter.Action.NONE
} }
else -> Filter.Action.NONE else -> Filter.Action.NONE
} }
} }
@ -178,7 +172,7 @@ class NotificationsViewModel @Inject constructor(
}.fold( }.fold(
onSuccess = { onSuccess = {
// since the follow request has been responded, the notification can be deleted. The Ui will update automatically. // since the follow request has been responded, the notification can be deleted. The Ui will update automatically.
db.notificationsDao().delete(accountManager.activeAccount!!.id, notificationId) db.notificationsDao().delete(account.id, notificationId)
if (accept) { if (accept) {
// refresh the notifications so the new follow notification will be loaded // refresh the notifications so the new follow notification will be loaded
refreshTrigger.value++ refreshTrigger.value++
@ -230,33 +224,33 @@ class NotificationsViewModel @Inject constructor(
fun changeExpanded(expanded: Boolean, status: StatusViewData.Concrete) { fun changeExpanded(expanded: Boolean, status: StatusViewData.Concrete) {
viewModelScope.launch { viewModelScope.launch {
db.timelineStatusDao() db.timelineStatusDao()
.setExpanded(accountManager.activeAccount!!.id, status.id, expanded) .setExpanded(account.id, status.id, expanded)
} }
} }
fun changeContentShowing(isShowing: Boolean, status: StatusViewData.Concrete) { fun changeContentShowing(isShowing: Boolean, status: StatusViewData.Concrete) {
viewModelScope.launch { viewModelScope.launch {
db.timelineStatusDao() db.timelineStatusDao()
.setContentShowing(accountManager.activeAccount!!.id, status.id, isShowing) .setContentShowing(account.id, status.id, isShowing)
} }
} }
fun changeContentCollapsed(isCollapsed: Boolean, status: StatusViewData.Concrete) { fun changeContentCollapsed(isCollapsed: Boolean, status: StatusViewData.Concrete) {
viewModelScope.launch { viewModelScope.launch {
db.timelineStatusDao() db.timelineStatusDao()
.setContentCollapsed(accountManager.activeAccount!!.id, status.id, isCollapsed) .setContentCollapsed(account.id, status.id, isCollapsed)
} }
} }
fun remove(notificationId: String) { fun remove(notificationId: String) {
viewModelScope.launch { viewModelScope.launch {
db.notificationsDao().delete(accountManager.activeAccount!!.id, notificationId) db.notificationsDao().delete(account.id, notificationId)
} }
} }
fun clearWarning(status: StatusViewData.Concrete) { fun clearWarning(status: StatusViewData.Concrete) {
viewModelScope.launch { viewModelScope.launch {
db.timelineStatusDao().clearWarning(accountManager.activeAccount!!.id, status.actionableId) db.timelineStatusDao().clearWarning(account.id, status.actionableId)
} }
} }
@ -264,7 +258,7 @@ class NotificationsViewModel @Inject constructor(
viewModelScope.launch { viewModelScope.launch {
api.clearNotifications().fold( api.clearNotifications().fold(
{ {
db.notificationsDao().cleanupNotifications(accountManager.activeAccount!!.id, 0) db.notificationsDao().cleanupNotifications(account.id, 0)
}, },
{ t -> { t ->
Log.w(TAG, "failed to clear notifications", t) Log.w(TAG, "failed to clear notifications", t)
@ -293,17 +287,16 @@ class NotificationsViewModel @Inject constructor(
try { try {
val notificationsDao = db.notificationsDao() val notificationsDao = db.notificationsDao()
val activeAccount = accountManager.activeAccount!!
notificationsDao.insertNotification( notificationsDao.insertNotification(
Placeholder(placeholderId, loading = true).toNotificationEntity( Placeholder(placeholderId, loading = true).toNotificationEntity(
activeAccount.id account.id
) )
) )
val response = db.withTransaction { val response = db.withTransaction {
val idAbovePlaceholder = notificationsDao.getIdAbove(activeAccount.id, placeholderId) val idAbovePlaceholder = notificationsDao.getIdAbove(account.id, placeholderId)
val idBelowPlaceholder = notificationsDao.getIdBelow(activeAccount.id, placeholderId) val idBelowPlaceholder = notificationsDao.getIdBelow(account.id, placeholderId)
when (readingOrder) { when (readingOrder) {
// Using minId, loads up to LOAD_AT_ONCE statuses with IDs immediately // Using minId, loads up to LOAD_AT_ONCE statuses with IDs immediately
// after minId and no larger than maxId // after minId and no larger than maxId
@ -334,11 +327,11 @@ class NotificationsViewModel @Inject constructor(
val accountDao = db.timelineAccountDao() val accountDao = db.timelineAccountDao()
db.withTransaction { db.withTransaction {
notificationsDao.delete(activeAccount.id, placeholderId) notificationsDao.delete(account.id, placeholderId)
val overlappedNotifications = if (notifications.isNotEmpty()) { val overlappedNotifications = if (notifications.isNotEmpty()) {
notificationsDao.deleteRange( notificationsDao.deleteRange(
activeAccount.id, account.id,
notifications.last().id, notifications.last().id,
notifications.first().id notifications.first().id
) )
@ -347,26 +340,28 @@ class NotificationsViewModel @Inject constructor(
} }
for (notification in notifications) { for (notification in notifications) {
accountDao.insert(notification.account.toEntity(activeAccount.id)) accountDao.insert(notification.account.toEntity(account.id))
notification.report?.let { report -> notification.report?.let { report ->
accountDao.insert(report.targetAccount.toEntity(activeAccount.id)) accountDao.insert(report.targetAccount.toEntity(account.id))
notificationsDao.insertReport(report.toEntity(activeAccount.id)) notificationsDao.insertReport(report.toEntity(account.id))
} }
notification.status?.let { status -> notification.status?.let { status ->
accountDao.insert(status.account.toEntity(activeAccount.id)) accountDao.insert(status.account.toEntity(account.id))
// get updated account in case some prefs have changed
val account = accountManager.getAccountById(this@NotificationsViewModel.account.id) ?: this@NotificationsViewModel.account
statusDao.insert( statusDao.insert(
status.toEntity( status.toEntity(
tuskyAccountId = activeAccount.id, tuskyAccountId = account.id,
expanded = activeAccount.alwaysOpenSpoiler, expanded = account.alwaysOpenSpoiler,
contentShowing = activeAccount.alwaysShowSensitiveMedia || !status.sensitive, contentShowing = account.alwaysShowSensitiveMedia || !status.sensitive,
contentCollapsed = true contentCollapsed = true
) )
) )
} }
notificationsDao.insertNotification( notificationsDao.insertNotification(
notification.toEntity( notification.toEntity(
activeAccount.id account.id
) )
) )
} }
@ -385,7 +380,7 @@ class NotificationsViewModel @Inject constructor(
Placeholder( Placeholder(
idToConvert, idToConvert,
loading = false loading = false
).toNotificationEntity(activeAccount.id) ).toNotificationEntity(account.id)
) )
} }
} }

View File

@ -45,7 +45,6 @@ import com.keylesspalace.tusky.entity.Poll
import com.keylesspalace.tusky.network.FilterModel import com.keylesspalace.tusky.network.FilterModel
import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.usecase.TimelineCases import com.keylesspalace.tusky.usecase.TimelineCases
import com.keylesspalace.tusky.util.EmptyPagingSource
import com.keylesspalace.tusky.viewdata.StatusViewData import com.keylesspalace.tusky.viewdata.StatusViewData
import com.keylesspalace.tusky.viewdata.TranslationViewData import com.keylesspalace.tusky.viewdata.TranslationViewData
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
@ -78,6 +77,8 @@ class CachedTimelineViewModel @Inject constructor(
filterModel filterModel
) { ) {
private val account = accountManager.activeAccount!!
private var currentPagingSource: PagingSource<Int, HomeTimelineData>? = null private var currentPagingSource: PagingSource<Int, HomeTimelineData>? = null
/** Map from status id to translation. */ /** Map from status id to translation. */
@ -90,12 +91,7 @@ class CachedTimelineViewModel @Inject constructor(
), ),
remoteMediator = CachedTimelineRemoteMediator(accountManager, api, db), remoteMediator = CachedTimelineRemoteMediator(accountManager, api, db),
pagingSourceFactory = { pagingSourceFactory = {
val activeAccount = accountManager.activeAccount db.timelineDao().getHomeTimeline(account.id).also { newPagingSource ->
if (activeAccount == null) {
EmptyPagingSource()
} else {
db.timelineDao().getHomeTimeline(activeAccount.id)
}.also { newPagingSource ->
this.currentPagingSource = newPagingSource this.currentPagingSource = newPagingSource
} }
} }
@ -125,27 +121,27 @@ class CachedTimelineViewModel @Inject constructor(
override fun changeExpanded(expanded: Boolean, status: StatusViewData.Concrete) { override fun changeExpanded(expanded: Boolean, status: StatusViewData.Concrete) {
viewModelScope.launch { viewModelScope.launch {
db.timelineStatusDao() db.timelineStatusDao()
.setExpanded(accountManager.activeAccount!!.id, status.actionableId, expanded) .setExpanded(account.id, status.actionableId, expanded)
} }
} }
override fun changeContentShowing(isShowing: Boolean, status: StatusViewData.Concrete) { override fun changeContentShowing(isShowing: Boolean, status: StatusViewData.Concrete) {
viewModelScope.launch { viewModelScope.launch {
db.timelineStatusDao() db.timelineStatusDao()
.setContentShowing(accountManager.activeAccount!!.id, status.actionableId, isShowing) .setContentShowing(account.id, status.actionableId, isShowing)
} }
} }
override fun changeContentCollapsed(isCollapsed: Boolean, status: StatusViewData.Concrete) { override fun changeContentCollapsed(isCollapsed: Boolean, status: StatusViewData.Concrete) {
viewModelScope.launch { viewModelScope.launch {
db.timelineStatusDao() db.timelineStatusDao()
.setContentCollapsed(accountManager.activeAccount!!.id, status.actionableId, isCollapsed) .setContentCollapsed(account.id, status.actionableId, isCollapsed)
} }
} }
override fun clearWarning(status: StatusViewData.Concrete) { override fun clearWarning(status: StatusViewData.Concrete) {
viewModelScope.launch { viewModelScope.launch {
db.timelineStatusDao().clearWarning(accountManager.activeAccount!!.id, status.actionableId) db.timelineStatusDao().clearWarning(account.id, status.actionableId)
} }
} }
@ -160,17 +156,13 @@ class CachedTimelineViewModel @Inject constructor(
val statusDao = db.timelineStatusDao() val statusDao = db.timelineStatusDao()
val accountDao = db.timelineAccountDao() val accountDao = db.timelineAccountDao()
val activeAccount = accountManager.activeAccount!!
timelineDao.insertHomeTimelineItem( timelineDao.insertHomeTimelineItem(
Placeholder(placeholderId, loading = true).toEntity( Placeholder(placeholderId, loading = true).toEntity(tuskyAccountId = account.id)
activeAccount.id
)
) )
val response = db.withTransaction { val response = db.withTransaction {
val idAbovePlaceholder = timelineDao.getIdAbove(activeAccount.id, placeholderId) val idAbovePlaceholder = timelineDao.getIdAbove(account.id, placeholderId)
val idBelowPlaceholder = timelineDao.getIdBelow(activeAccount.id, placeholderId) val idBelowPlaceholder = timelineDao.getIdBelow(account.id, placeholderId)
when (readingOrder) { when (readingOrder) {
// Using minId, loads up to LOAD_AT_ONCE statuses with IDs immediately // Using minId, loads up to LOAD_AT_ONCE statuses with IDs immediately
// after minId and no larger than maxId // after minId and no larger than maxId
@ -196,11 +188,11 @@ class CachedTimelineViewModel @Inject constructor(
} }
db.withTransaction { db.withTransaction {
timelineDao.deleteHomeTimelineItem(activeAccount.id, placeholderId) timelineDao.deleteHomeTimelineItem(account.id, placeholderId)
val overlappedStatuses = if (statuses.isNotEmpty()) { val overlappedStatuses = if (statuses.isNotEmpty()) {
timelineDao.deleteRange( timelineDao.deleteRange(
activeAccount.id, account.id,
statuses.last().id, statuses.last().id,
statuses.first().id statuses.first().id
) )
@ -209,22 +201,23 @@ class CachedTimelineViewModel @Inject constructor(
} }
for (status in statuses) { for (status in statuses) {
accountDao.insert(status.account.toEntity(activeAccount.id)) accountDao.insert(status.account.toEntity(account.id))
status.reblog?.account?.toEntity(activeAccount.id) status.reblog?.account?.toEntity(account.id)
?.let { rebloggedAccount -> ?.let { rebloggedAccount ->
accountDao.insert(rebloggedAccount) accountDao.insert(rebloggedAccount)
} }
val account = accountManager.getAccountById(account.id) ?: return@withTransaction
statusDao.insert( statusDao.insert(
status.actionableStatus.toEntity( status.actionableStatus.toEntity(
tuskyAccountId = activeAccount.id, tuskyAccountId = account.id,
expanded = activeAccount.alwaysOpenSpoiler, expanded = account.alwaysOpenSpoiler,
contentShowing = activeAccount.alwaysShowSensitiveMedia || !status.actionableStatus.sensitive, contentShowing = account.alwaysShowSensitiveMedia || !status.actionableStatus.sensitive,
contentCollapsed = true contentCollapsed = true
) )
) )
timelineDao.insertHomeTimelineItem( timelineDao.insertHomeTimelineItem(
HomeTimelineEntity( HomeTimelineEntity(
tuskyAccountId = activeAccount.id, tuskyAccountId = account.id,
id = status.id, id = status.id,
statusId = status.actionableId, statusId = status.actionableId,
reblogAccountId = if (status.reblog != null) { reblogAccountId = if (status.reblog != null) {
@ -250,7 +243,7 @@ class CachedTimelineViewModel @Inject constructor(
Placeholder( Placeholder(
idToConvert, idToConvert,
loading = false loading = false
).toEntity(activeAccount.id) ).toEntity(account.id)
) )
} }
} }
@ -286,7 +279,7 @@ class CachedTimelineViewModel @Inject constructor(
override suspend fun invalidate() { override suspend fun invalidate() {
// invalidating when we don't have statuses yet can cause empty timelines because it cancels the network load // invalidating when we don't have statuses yet can cause empty timelines because it cancels the network load
if (db.timelineDao().getHomeTimelineItemCount(accountManager.activeAccount!!.id) > 0) { if (db.timelineDao().getHomeTimelineItemCount(account.id) > 0) {
currentPagingSource?.invalidate() currentPagingSource?.invalidate()
} }
} }