feat: run EventSync in background by scoping it to AccountViewModel

Move EventSyncViewModel from a screen-scoped ViewModel to a plain class
held by AccountViewModel (activity-scoped). The sync job now runs on
AccountViewModel's viewModelScope, so it continues when the user
navigates away. The back button no longer cancels the sync.

https://claude.ai/code/session_01U8qF9mK4UBvXsXP1zNMXfX
This commit is contained in:
Claude
2026-03-13 22:42:09 +00:00
parent 441dae8e25
commit 90d7b0c678
3 changed files with 11 additions and 19 deletions
@@ -79,6 +79,7 @@ import com.vitorpamplona.amethyst.ui.note.showAmount
import com.vitorpamplona.amethyst.ui.note.showAmountInteger
import com.vitorpamplona.amethyst.ui.screen.UiSettingsState
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.CombinedZap
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.eventsync.EventSyncViewModel
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.tor.TorSettingsFlow
import com.vitorpamplona.amethyst.ui.tor.TorType
@@ -175,6 +176,7 @@ class AccountViewModel(
val toastManager = ToastManager()
val broadcastTracker = BroadcastTracker()
val feedStates = AccountFeedContentStates(account, viewModelScope)
val eventSyncViewModel = EventSyncViewModel(account, viewModelScope)
val tempManualPaymentCache = LruCache<String, List<ZapPaymentHandler.Payable>>(5)
@@ -48,7 +48,6 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarWithBackButton
@@ -60,8 +59,7 @@ fun EventSyncScreen(
accountViewModel: AccountViewModel,
nav: INav,
) {
val syncViewModel: EventSyncViewModel =
viewModel(factory = EventSyncViewModel.Factory(accountViewModel.account))
val syncViewModel = accountViewModel.eventSyncViewModel
val syncState by syncViewModel.syncState.collectAsStateWithLifecycle()
val isMobileOrMetered by accountViewModel.settings.isMobileOrMeteredConnection.collectAsStateWithLifecycle()
@@ -70,10 +68,7 @@ fun EventSyncScreen(
topBar = {
TopBarWithBackButton(
caption = stringRes(R.string.event_sync_title),
popBack = {
syncViewModel.cancel()
nav.popBack()
},
popBack = nav::popBack,
)
},
) { padding ->
@@ -21,15 +21,13 @@
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.eventsync
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.IRequestListener
import com.vitorpamplona.quartz.nip01Core.relay.client.single.newSubId
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.Channel
@@ -47,10 +45,14 @@ import kotlinx.coroutines.withTimeoutOrNull
*
* The procedure iterates over every relay URL in LocalCache.relayHints.relayDB.
* Events are deduplicated per destination to avoid redundant sends.
*
* Scoped to the AccountViewModel so the sync continues in the background while
* the user navigates to other screens or away from the app.
*/
class EventSyncViewModel(
val account: Account,
) : ViewModel() {
private val scope: CoroutineScope,
) {
sealed class SyncState {
object Idle : SyncState()
@@ -81,7 +83,7 @@ class EventSyncViewModel(
fun start() {
if (_syncState.value is SyncState.Running) return
syncJob =
viewModelScope.launch(Dispatchers.IO) {
scope.launch(Dispatchers.IO) {
runSync()
}
}
@@ -276,11 +278,4 @@ class EventSyncViewModel(
account.client.close(subId)
done.close()
}
class Factory(
val account: Account,
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T = EventSyncViewModel(account) as T
}
}