diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt index 101611da1..d2cc30fb6 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt @@ -81,7 +81,7 @@ import kotlinx.coroutines.withContext @Composable fun JoinUserOrChannelView(onClose: () -> Unit, accountViewModel: AccountViewModel, nav: (String) -> Unit) { val searchBarViewModel: SearchBarViewModel = viewModel( - key = accountViewModel.account.userProfile().pubkeyHex + "SearchBarViewModel", + key = "SearchBarViewModel", factory = SearchBarViewModel.Factory( accountViewModel.account ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt index 2c96c5376..c698e3691 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt @@ -2,6 +2,7 @@ package com.vitorpamplona.amethyst.ui.actions import android.content.Context import android.net.Uri +import android.util.Log import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateListOf @@ -631,6 +632,7 @@ open class NewPostViewModel() : ViewModel() { override fun onCleared() { super.onCleared() + Log.d("Init", "OnCleared: ${this.javaClass.simpleName}") viewModelScope.launch(Dispatchers.IO) { locUtil?.stop() } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt index cf58f405f..ee0e89ce2 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt @@ -667,6 +667,7 @@ class FollowListViewModel(val account: Account) : ViewModel() { override fun onCleared() { collectorJob?.cancel() + Log.d("Init", "OnCleared: ${this.javaClass.simpleName}") super.onCleared() } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNote.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNote.kt index b4fc83249..2e4cc6cc0 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNote.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNote.kt @@ -81,9 +81,7 @@ fun PollNote( accountViewModel: AccountViewModel, nav: (String) -> Unit ) { - val pollViewModel: PollNoteViewModel = viewModel( - key = baseNote.idHex + "PollNoteViewModel" - ) + val pollViewModel: PollNoteViewModel = viewModel(key = "PollNoteViewModel") pollViewModel.load(accountViewModel.account, baseNote) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateReactionTypeDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateReactionTypeDialog.kt index 13d239407..515c2b7d7 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateReactionTypeDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateReactionTypeDialog.kt @@ -127,7 +127,7 @@ fun UpdateReactionTypeDialog( nav: (String) -> Unit ) { val postViewModel: UpdateReactionTypeViewModel = viewModel( - key = accountViewModel.userProfile().pubkeyHex, + key = "UpdateReactionTypeViewModel", factory = UpdateReactionTypeViewModel.Factory(accountViewModel.account) ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt index e1f756981..0ba79a4f1 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt @@ -204,7 +204,7 @@ fun UpdateZapAmountDialog( val scope = rememberCoroutineScope() val postViewModel: UpdateZapAmountViewModel = viewModel( - key = accountViewModel.userProfile().pubkeyHex, + key = "UpdateZapAmountViewModel", factory = UpdateZapAmountViewModel.Factory(accountViewModel.account) ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt index 50d0bfed8..15e7afa1f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserReactionsRow.kt @@ -366,6 +366,7 @@ class UserReactionsViewModel(val account: Account) : ViewModel() { override fun onCleared() { collectorJob?.cancel() bundlerInsert.cancel() + Log.d("Init", "OnCleared: ${this.javaClass.simpleName}") super.onCleared() } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountScreen.kt index f409f25b7..a819d28ef 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountScreen.kt @@ -8,13 +8,18 @@ import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.lifecycle.ViewModelStore +import androidx.lifecycle.ViewModelStoreOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.MainScreen import com.vitorpamplona.amethyst.ui.screen.loggedOff.LoginPage @@ -40,26 +45,53 @@ fun AccountScreen( LoginPage(accountStateViewModel, isFirstLogin = true) } is AccountState.LoggedIn -> { - val accountViewModel: AccountViewModel = viewModel( - key = state.account.hashCode().toString(), - factory = AccountViewModel.Factory(state.account, sharedPreferencesViewModel.sharedPrefs) - ) - - MainScreen(accountViewModel, accountStateViewModel, sharedPreferencesViewModel) + CompositionLocalProvider( + LocalViewModelStoreOwner provides state.currentViewModelStore + ) { + LoggedInPage( + state.account, + accountStateViewModel, + sharedPreferencesViewModel + ) + } } is AccountState.LoggedInViewOnly -> { - val accountViewModel: AccountViewModel = viewModel( - key = state.account.hashCode().toString(), - factory = AccountViewModel.Factory(state.account, sharedPreferencesViewModel.sharedPrefs) - ) - - MainScreen(accountViewModel, accountStateViewModel, sharedPreferencesViewModel) + CompositionLocalProvider( + LocalViewModelStoreOwner provides state.currentViewModelStore + ) { + LoggedInPage( + state.account, + accountStateViewModel, + sharedPreferencesViewModel + ) + } } } } } } +@Composable +fun LoggedInPage( + account: Account, + accountStateViewModel: AccountStateViewModel, + sharedPreferencesViewModel: SharedPreferencesViewModel +) { + val accountViewModel: AccountViewModel = viewModel( + key = "AccountStateViewModel", + factory = AccountViewModel.Factory( + account, + sharedPreferencesViewModel.sharedPrefs + ) + ) + + MainScreen(accountViewModel, accountStateViewModel, sharedPreferencesViewModel) +} + +class AccountCentricViewModelStore(val account: Account) : ViewModelStoreOwner { + override val viewModelStore = ViewModelStore() +} + @Composable fun LoadingAccounts() { Column( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountState.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountState.kt index 2a687608f..72c3623a2 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountState.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountState.kt @@ -5,6 +5,10 @@ import com.vitorpamplona.amethyst.model.Account sealed class AccountState { object Loading : AccountState() object LoggedOff : AccountState() - class LoggedInViewOnly(val account: Account) : AccountState() - class LoggedIn(val account: Account) : AccountState() + class LoggedInViewOnly(val account: Account) : AccountState() { + val currentViewModelStore = AccountCentricViewModelStore(account) + } + class LoggedIn(val account: Account) : AccountState() { + val currentViewModelStore = AccountCentricViewModelStore(account) + } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountStateViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountStateViewModel.kt index 61ffed82a..131a14533 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountStateViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountStateViewModel.kt @@ -103,9 +103,15 @@ class AccountStateViewModel() : ViewModel() { when (val state = _accountContent.value) { is AccountState.LoggedIn -> { state.account.saveable.removeObserver(saveListener) + withContext(Dispatchers.IO) { + state.currentViewModelStore.viewModelStore.clear() + } } is AccountState.LoggedInViewOnly -> { state.account.saveable.removeObserver(saveListener) + withContext(Dispatchers.IO) { + state.currentViewModelStore.viewModelStore.clear() + } } else -> {} } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt index 58ad48293..7975ab545 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt @@ -370,6 +370,7 @@ open class CardFeedViewModel(val localFilter: FeedFilter) : ViewModel() { } override fun onCleared() { + Log.d("Init", "OnCleared: ${this.javaClass.simpleName}") clear() bundlerInsert.cancel() bundler.cancel() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt index b37a68596..950f54dc7 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt @@ -322,7 +322,7 @@ abstract class FeedViewModel(val localFilter: FeedFilter) : ViewModel(), I private var collectorJob: Job? = null init { - Log.d("Init", "${this.javaClass.simpleName}") + Log.d("Init", "Starting new Model: ${this.javaClass.simpleName}") collectorJob = viewModelScope.launch(Dispatchers.IO) { LocalCache.live.newEventBundles.collect { newNotes -> checkNotInMainThread() @@ -340,6 +340,7 @@ abstract class FeedViewModel(val localFilter: FeedFilter) : ViewModel(), I } override fun onCleared() { + Log.d("Init", "OnCleared: ${this.javaClass.simpleName}") bundlerInsert.cancel() bundler.cancel() collectorJob?.cancel() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedViewModel.kt index ee769b98c..754144202 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedViewModel.kt @@ -95,6 +95,7 @@ open class LnZapFeedViewModel(val dataSource: FeedFilter) : View } override fun onCleared() { + Log.d("Init", "OnCleared: ${this.javaClass.simpleName}") bundler.cancel() collectorJob?.cancel() super.onCleared() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/RelayFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/RelayFeedView.kt index 75326e102..d3aa0f990 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/RelayFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/RelayFeedView.kt @@ -1,5 +1,6 @@ package com.vitorpamplona.amethyst.ui.screen +import android.util.Log import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues @@ -96,6 +97,7 @@ class RelayFeedViewModel : ViewModel() { } override fun onCleared() { + Log.d("Init", "OnCleared: ${this.javaClass.simpleName}") bundler.cancel() super.onCleared() } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/StringFeedViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/StringFeedViewModel.kt index fe17b88c9..41a35926b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/StringFeedViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/StringFeedViewModel.kt @@ -100,6 +100,7 @@ open class StringFeedViewModel(val dataSource: FeedFilter) : ViewModel() } override fun onCleared() { + Log.d("Init", "OnCleared: ${this.javaClass.simpleName}") bundler.cancel() collectorJob?.cancel() super.onCleared() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt index 608d86ce7..01afe18ed 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt @@ -126,6 +126,7 @@ open class UserFeedViewModel(val dataSource: FeedFilter) : ViewModel(), In } override fun onCleared() { + Log.d("Init", "OnCleared: ${this.javaClass.simpleName}") bundler.cancel() collectorJob?.cancel() super.onCleared() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt index 1bf26fc70..66362fb0e 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt @@ -792,7 +792,7 @@ class AccountViewModel(val account: Account, val settings: SettingsState) : View } } - fun refreshMarkAsReadObservers() { + suspend fun refreshMarkAsReadObservers() { updateNotificationDots() accountMarkAsReadUpdates.value++ } @@ -862,25 +862,26 @@ class AccountViewModel(val account: Account, val settings: SettingsState) : View private var collectorJob: Job? = null val notificationDots = HasNotificationDot(bottomNavigationItems, account) - fun updateNotificationDots(newNotes: Set = emptySet()) { - viewModelScope.launch(Dispatchers.Default) { - val (value, elapsed) = measureTimedValue { - notificationDots.update(newNotes) - } - Log.d("Rendering Metrics", "Notification Dots Calculation in $elapsed for ${newNotes.size} new notes") + suspend fun updateNotificationDots(newNotes: Set = emptySet()) { + val (value, elapsed) = measureTimedValue { + notificationDots.update(newNotes) } + Log.d("Rendering Metrics", "Notification Dots Calculation in $elapsed for ${newNotes.size} new notes") } init { Log.d("Init", "AccountViewModel") collectorJob = viewModelScope.launch(Dispatchers.IO) { LocalCache.live.newEventBundles.collect { newNotes -> + Log.d("Rendering Metrics", "Notification Dots Calculation refresh ${this@AccountViewModel}") + updateNotificationDots(newNotes) } } } override fun onCleared() { + Log.d("Init", "AccountViewModel onCleared") collectorJob?.cancel() super.onCleared() } @@ -920,11 +921,16 @@ class HasNotificationDot(bottomNavigationItems: ImmutableList, val accoun val hasNewItems = bottomNavigationItems.associateWith { MutableStateFlow(false) } fun update(newNotes: Set) { + checkNotInMainThread() + hasNewItems.forEach { - val newResult = it.key.hasNewItems(account, newNotes) - if (newResult != it.value.value) { - it.value.value = newResult + val (value, elapsed) = measureTimedValue { + val newResult = it.key.hasNewItems(account, newNotes) + if (newResult != it.value.value) { + it.value.value = newResult + } } + Log.d("Rendering Metrics", "Notification Dots Calculation for ${it.key.route} in $elapsed for ${newNotes.size} new notes") } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/MainScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/MainScreen.kt index 5f83280c5..6ee9ea7bd 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/MainScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/MainScreen.kt @@ -141,58 +141,58 @@ fun MainScreen( } val followLists: FollowListViewModel = viewModel( - key = accountViewModel.userProfile().pubkeyHex + "FollowListViewModel", + key = "FollowListViewModel", factory = FollowListViewModel.Factory(accountViewModel.account) ) // Avoids creating ViewModels for performance reasons (up to 1 second delays) val homeFeedViewModel: NostrHomeFeedViewModel = viewModel( - key = accountViewModel.userProfile().pubkeyHex + "NostrHomeFeedViewModel", + key = "NostrHomeFeedViewModel", factory = NostrHomeFeedViewModel.Factory(accountViewModel.account) ) val repliesFeedViewModel: NostrHomeRepliesFeedViewModel = viewModel( - key = accountViewModel.userProfile().pubkeyHex + "NostrHomeRepliesFeedViewModel", + key = "NostrHomeRepliesFeedViewModel", factory = NostrHomeRepliesFeedViewModel.Factory(accountViewModel.account) ) val videoFeedViewModel: NostrVideoFeedViewModel = viewModel( - key = accountViewModel.userProfile().pubkeyHex + "NostrVideoFeedViewModel", + key = "NostrVideoFeedViewModel", factory = NostrVideoFeedViewModel.Factory(accountViewModel.account) ) val discoveryLiveFeedViewModel: NostrDiscoverLiveFeedViewModel = viewModel( - key = accountViewModel.userProfile().pubkeyHex + "NostrDiscoveryLiveFeedViewModel", + key = "NostrDiscoveryLiveFeedViewModel", factory = NostrDiscoverLiveFeedViewModel.Factory(accountViewModel.account) ) val discoveryCommunityFeedViewModel: NostrDiscoverCommunityFeedViewModel = viewModel( - key = accountViewModel.userProfile().pubkeyHex + "NostrDiscoveryCommunityFeedViewModel", + key = "NostrDiscoveryCommunityFeedViewModel", factory = NostrDiscoverCommunityFeedViewModel.Factory(accountViewModel.account) ) val discoveryChatFeedViewModel: NostrDiscoverChatFeedViewModel = viewModel( - key = accountViewModel.userProfile().pubkeyHex + "NostrDiscoveryChatFeedViewModel", + key = "NostrDiscoveryChatFeedViewModel", factory = NostrDiscoverChatFeedViewModel.Factory(accountViewModel.account) ) val notifFeedViewModel: NotificationViewModel = viewModel( - key = accountViewModel.userProfile().pubkeyHex + "NotificationViewModel", + key = "NotificationViewModel", factory = NotificationViewModel.Factory(accountViewModel.account) ) val userReactionsStatsModel: UserReactionsViewModel = viewModel( - key = accountViewModel.userProfile().pubkeyHex + "UserReactionsViewModel", + key = "UserReactionsViewModel", factory = UserReactionsViewModel.Factory(accountViewModel.account) ) val knownFeedViewModel: NostrChatroomListKnownFeedViewModel = viewModel( - key = accountViewModel.userProfile().pubkeyHex + "NostrChatroomListKnownFeedViewModel", + key = "NostrChatroomListKnownFeedViewModel", factory = NostrChatroomListKnownFeedViewModel.Factory(accountViewModel.account) ) val newFeedViewModel: NostrChatroomListNewFeedViewModel = viewModel( - key = accountViewModel.userProfile().pubkeyHex + "NostrChatroomListNewFeedViewModel", + key = "NostrChatroomListNewFeedViewModel", factory = NostrChatroomListNewFeedViewModel.Factory(accountViewModel.account) ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt index 1234bd261..ae8620f05 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt @@ -1,5 +1,6 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn +import android.util.Log import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -86,7 +87,7 @@ fun SearchScreen( nav: (String) -> Unit ) { val searchBarViewModel: SearchBarViewModel = viewModel( - key = accountViewModel.account.userProfile().pubkeyHex + "SearchBarViewModel", + key = "SearchBarViewModel", factory = SearchBarViewModel.Factory( accountViewModel.account ) @@ -198,6 +199,7 @@ class SearchBarViewModel(val account: Account) : ViewModel() { override fun onCleared() { bundler.cancel() + Log.d("Init", "OnCleared: ${this.javaClass.simpleName}") super.onCleared() }