diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt index 2baf82bd2..3a194b321 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt @@ -449,10 +449,12 @@ private fun ObserveNIP19Event( var baseNote by remember(it) { mutableStateOf(null) } LaunchedEffect(key1 = it.hex) { - launch(Dispatchers.IO) { - if (it.type == Nip19.Type.NOTE || it.type == Nip19.Type.EVENT || it.type == Nip19.Type.ADDRESS) { - LocalCache.checkGetOrCreateNote(it.hex)?.let { note -> - baseNote = note + if (baseNote == null) { + launch(Dispatchers.IO) { + if (it.type == Nip19.Type.NOTE || it.type == Nip19.Type.EVENT || it.type == Nip19.Type.ADDRESS) { + LocalCache.checkGetOrCreateNote(it.hex)?.let { note -> + baseNote = note + } } } } @@ -479,10 +481,12 @@ private fun ObserveNIP19User( var baseUser by remember(it) { mutableStateOf(null) } LaunchedEffect(key1 = it.hex) { - launch(Dispatchers.IO) { - if (it.type == Nip19.Type.USER) { - LocalCache.checkGetOrCreateUser(it.hex)?.let { user -> - baseUser = user + if (baseUser == null) { + launch(Dispatchers.IO) { + if (it.type == Nip19.Type.USER) { + LocalCache.checkGetOrCreateUser(it.hex)?.let { user -> + baseUser = user + } } } } @@ -782,27 +786,29 @@ fun TagLink(word: String, tags: List>, canPreview: Boolean, backgro var baseNotePair by remember { mutableStateOf?>(null) } LaunchedEffect(key1 = word) { - launch(Dispatchers.IO) { - val matcher = tagIndex.matcher(word) - val (index, suffix) = try { - matcher.find() - Pair(matcher.group(1)?.toInt(), matcher.group(2) ?: "") - } catch (e: Exception) { - Log.w("Tag Parser", "Couldn't link tag $word", e) - Pair(null, null) - } + if (baseUserPair == null && baseNotePair == null) { + launch(Dispatchers.IO) { + val matcher = tagIndex.matcher(word) + val (index, suffix) = try { + matcher.find() + Pair(matcher.group(1)?.toInt(), matcher.group(2) ?: "") + } catch (e: Exception) { + Log.w("Tag Parser", "Couldn't link tag $word", e) + Pair(null, null) + } - if (index != null && index >= 0 && index < tags.size) { - val tag = tags[index] + if (index != null && index >= 0 && index < tags.size) { + val tag = tags[index] - if (tag.size > 1) { - if (tag[0] == "p") { - LocalCache.checkGetOrCreateUser(tag[1])?.let { - baseUserPair = Pair(it, suffix) - } - } else if (tag[0] == "e" || tag[0] == "a") { - LocalCache.checkGetOrCreateNote(tag[1])?.let { - baseNotePair = Pair(it, suffix) + if (tag.size > 1) { + if (tag[0] == "p") { + LocalCache.checkGetOrCreateUser(tag[1])?.let { + baseUserPair = Pair(it, suffix) + } + } else if (tag[0] == "e" || tag[0] == "a") { + LocalCache.checkGetOrCreateNote(tag[1])?.let { + baseNotePair = Pair(it, suffix) + } } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppBottomBar.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppBottomBar.kt index 664311755..4148fd738 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppBottomBar.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppBottomBar.kt @@ -110,31 +110,11 @@ private fun RowScope.HasNewItemsIcon( accountViewModel: AccountViewModel, navController: NavHostController ) { - val accountState by accountViewModel.accountLiveData.observeAsState() - val account = remember(accountState) { accountState?.account } ?: return + var hasNewItems by remember { mutableStateOf(false) } - val notifState by NotificationCache.live.observeAsState() - val notif = remember(notifState) { notifState?.cache } ?: return - - var hasNewItems by remember { mutableStateOf(false) } - - LaunchedEffect(key1 = notifState, key2 = accountState) { - launch(Dispatchers.IO) { - val newHasNewItems = route.hasNewItems(account, notif, emptySet()) - if (newHasNewItems != hasNewItems) { - hasNewItems = newHasNewItems - } - } - } - - LaunchedEffect(Unit) { - launch(Dispatchers.IO) { - LocalCache.live.newEventBundles.collect { - val newHasNewItems = route.hasNewItems(account, notif, it) - if (newHasNewItems != hasNewItems) { - hasNewItems = newHasNewItems - } - } + WatchPossibleNotificationChanges(route, accountViewModel) { + if (it != hasNewItems) { + hasNewItems = it } } @@ -167,6 +147,33 @@ private fun RowScope.HasNewItemsIcon( } } +@Composable +fun WatchPossibleNotificationChanges( + route: Route, + accountViewModel: AccountViewModel, + onChange: (Boolean) -> Unit +) { + val accountState by accountViewModel.accountLiveData.observeAsState() + val account = remember(accountState) { accountState?.account } ?: return + + val notifState by NotificationCache.live.observeAsState() + val notif = remember(notifState) { notifState?.cache } ?: return + + LaunchedEffect(key1 = notifState, key2 = accountState) { + launch(Dispatchers.IO) { + onChange(route.hasNewItems(account, notif, emptySet())) + } + } + + LaunchedEffect(Unit) { + launch(Dispatchers.IO) { + LocalCache.live.newEventBundles.collect { + onChange(route.hasNewItems(account, notif, it)) + } + } + } +} + @Composable private fun RowScope.BottomIcon( icon: Int, 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 7cfd38238..6f3152688 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 @@ -54,7 +54,6 @@ import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS import com.vitorpamplona.amethyst.model.KIND3_FOLLOWS import com.vitorpamplona.amethyst.model.LocalCache -import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.NostrAccountDataSource import com.vitorpamplona.amethyst.service.NostrChannelDataSource import com.vitorpamplona.amethyst.service.NostrChatroomDataSource @@ -103,16 +102,20 @@ fun AppTopBar(followLists: FollowListViewModel, navController: NavHostController fun StoriesTopBar(followLists: FollowListViewModel, scaffoldState: ScaffoldState, accountViewModel: AccountViewModel) { GenericTopBar(scaffoldState, accountViewModel) { accountViewModel -> val accountState by accountViewModel.accountLiveData.observeAsState() - accountState?.account?.let { account -> - FollowList( - followLists, - account.defaultStoriesFollowList, - account.userProfile(), - true - ) { listName -> - account.changeDefaultStoriesFollowList(listName) + + val list by remember(accountState) { + derivedStateOf { + accountState?.account?.defaultStoriesFollowList ?: GLOBAL_FOLLOWS } } + + FollowList( + followLists, + list, + true + ) { listName -> + accountViewModel.account.changeDefaultStoriesFollowList(listName) + } } } @@ -120,16 +123,20 @@ fun StoriesTopBar(followLists: FollowListViewModel, scaffoldState: ScaffoldState fun HomeTopBar(followLists: FollowListViewModel, scaffoldState: ScaffoldState, accountViewModel: AccountViewModel) { GenericTopBar(scaffoldState, accountViewModel) { accountViewModel -> val accountState by accountViewModel.accountLiveData.observeAsState() - accountState?.account?.let { account -> - FollowList( - followLists, - account.defaultHomeFollowList, - account.userProfile(), - false - ) { listName -> - account.changeDefaultHomeFollowList(listName) + + val list by remember(accountState) { + derivedStateOf { + accountState?.account?.defaultHomeFollowList ?: GLOBAL_FOLLOWS } } + + FollowList( + followLists, + list, + false + ) { listName -> + accountViewModel.account.changeDefaultHomeFollowList(listName) + } } } @@ -137,16 +144,20 @@ fun HomeTopBar(followLists: FollowListViewModel, scaffoldState: ScaffoldState, a fun NotificationTopBar(followLists: FollowListViewModel, scaffoldState: ScaffoldState, accountViewModel: AccountViewModel) { GenericTopBar(scaffoldState, accountViewModel) { accountViewModel -> val accountState by accountViewModel.accountLiveData.observeAsState() - accountState?.account?.let { account -> - FollowList( - followLists, - account.defaultNotificationFollowList, - account.userProfile(), - true - ) { listName -> - account.changeDefaultNotificationFollowList(listName) + + val list by remember(accountState) { + derivedStateOf { + accountState?.account?.defaultNotificationFollowList ?: GLOBAL_FOLLOWS } } + + FollowList( + followLists, + list, + true + ) { listName -> + accountViewModel.account.changeDefaultNotificationFollowList(listName) + } } } @@ -296,7 +307,7 @@ private fun LoggedInUserPictureDrawer( } @Composable -fun FollowList(followListsModel: FollowListViewModel, listName: String, loggedIn: User, withGlobal: Boolean, onChange: (String) -> Unit) { +fun FollowList(followListsModel: FollowListViewModel, listName: String, withGlobal: Boolean, onChange: (String) -> Unit) { val kind3Follow = Pair(KIND3_FOLLOWS, stringResource(id = R.string.follow_list_kind3follows)) val globalFollow = Pair(GLOBAL_FOLLOWS, stringResource(id = R.string.follow_list_global)) @@ -339,7 +350,10 @@ class FollowListViewModel(val account: Account) : ViewModel() { val newFollowLists = LocalCache.addressables.mapNotNull { val event = (it.value.event as? PeopleListEvent) // Has to have an list - if (event != null && event.pubKey == account.userProfile().pubkeyHex && (event.tags.size > 1 || event.content.length > 50)) { + if (event != null && + event.pubKey == account.userProfile().pubkeyHex && + (event.tags.size > 1 || event.content.length > 50) + ) { Pair(event.dTag(), event.dTag()) } else { null diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt index f5e030cef..e6786de5d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt @@ -76,16 +76,13 @@ fun DrawerContent( sheetState: ModalBottomSheetState, accountViewModel: AccountViewModel ) { - val accountState by accountViewModel.accountLiveData.observeAsState() - val account = accountState?.account ?: return - Surface( modifier = Modifier.fillMaxWidth(), color = MaterialTheme.colors.background ) { Column() { ProfileContent( - account.userProfile(), + accountViewModel.account.userProfile(), modifier = Modifier .fillMaxWidth() .padding(horizontal = 25.dp) @@ -98,17 +95,16 @@ fun DrawerContent( modifier = Modifier.padding(top = 20.dp) ) ListContent( - account.userProfile().pubkeyHex, nav, scaffoldState, sheetState, modifier = Modifier .fillMaxWidth() .weight(1f), - account + accountViewModel ) - BottomContent(account.userProfile(), scaffoldState, nav) + BottomContent(accountViewModel.account.userProfile(), scaffoldState, nav) } } } @@ -265,13 +261,15 @@ private fun FollowingAndFollowerCounts(baseAccountUser: User) { @OptIn(ExperimentalMaterialApi::class) @Composable fun ListContent( - accountUserPubKey: String?, nav: (String) -> Unit, scaffoldState: ScaffoldState, sheetState: ModalBottomSheetState, modifier: Modifier, - account: Account + accountViewModel: AccountViewModel ) { + val accountState by accountViewModel.accountLiveData.observeAsState() + val account = remember(accountState) { accountState?.account } ?: return + val coroutineScope = rememberCoroutineScope() var backupDialogOpen by remember { mutableStateOf(false) } var checked by remember { mutableStateOf(account.proxy != null) } @@ -284,25 +282,23 @@ fun ListContent( .fillMaxHeight() .verticalScroll(rememberScrollState()) ) { - if (accountUserPubKey != null) { - NavigationRow( - title = stringResource(R.string.profile), - icon = Route.Profile.icon, - tint = MaterialTheme.colors.primary, - nav = nav, - scaffoldState = scaffoldState, - route = "User/$accountUserPubKey" - ) + NavigationRow( + title = stringResource(R.string.profile), + icon = Route.Profile.icon, + tint = MaterialTheme.colors.primary, + nav = nav, + scaffoldState = scaffoldState, + route = "User/${account.userProfile().pubkeyHex}" + ) - NavigationRow( - title = stringResource(R.string.bookmarks), - icon = Route.Bookmarks.icon, - tint = MaterialTheme.colors.onBackground, - nav = nav, - scaffoldState = scaffoldState, - route = Route.Bookmarks.route - ) - } + NavigationRow( + title = stringResource(R.string.bookmarks), + icon = Route.Bookmarks.icon, + tint = MaterialTheme.colors.onBackground, + nav = nav, + scaffoldState = scaffoldState, + route = Route.Bookmarks.route + ) NavigationRow( title = stringResource(R.string.security_filters), diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt index b6fc0c341..184efd244 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt @@ -65,9 +65,6 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun val noteState by baseNote.live().metadata.observeAsState() val note = remember(noteState) { noteState?.note } ?: return - val accountState by accountViewModel.accountLiveData.observeAsState() - val loggedIn = remember(accountState) { accountState?.account?.userProfile() } ?: return - var popupExpanded by remember { mutableStateOf(false) } val scope = rememberCoroutineScope() @@ -108,7 +105,7 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun .combinedClickable( onClick = { scope.launch { - routeFor(baseNote, loggedIn)?.let { nav(it) } + routeFor(baseNote, accountViewModel.userProfile())?.let { nav(it) } } }, onLongClick = { popupExpanded = true } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt index 4940819b7..62b577dc5 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NIP05VerificationDisplay.kt @@ -15,6 +15,7 @@ import androidx.compose.material.icons.filled.Report import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.State +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf @@ -97,11 +98,14 @@ fun nip05VerificationAsAState(user: UserMetadata, pubkeyHex: String): State Unit, clickablePrepend: @Composable () -> Unit ) { - val zapsState by baseNote.live().zaps.observeAsState() - val zappedNote = zapsState?.note ?: return + val isLoggedUser by remember { + derivedStateOf { + accountViewModel.isLoggedUser(baseNote.author) + } + } var wantsToZap by remember { mutableStateOf(false) } + var zappingProgress by remember { mutableStateOf(0f) } val context = LocalContext.current val scope = rememberCoroutineScope() - var zappingProgress by remember { mutableStateOf(0f) } - - val accountState by accountViewModel.accountLiveData.observeAsState() - val account = accountState?.account ?: return - nonClickablePrepend() Row( @@ -311,7 +310,7 @@ fun ZapVote( ) .show() } - } else if (accountViewModel.isLoggedUser(zappedNote.author)) { + } else if (isLoggedUser) { scope.launch { Toast .makeText( @@ -333,11 +332,13 @@ fun ZapVote( .show() } return@combinedClickable - } else if (account.zapAmountChoices.size == 1 && pollViewModel.isValidInputVoteAmount(account.zapAmountChoices.first())) { + } else if (accountViewModel.account.zapAmountChoices.size == 1 && + pollViewModel.isValidInputVoteAmount(accountViewModel.account.zapAmountChoices.first()) + ) { scope.launch(Dispatchers.IO) { accountViewModel.zap( baseNote, - account.zapAmountChoices.first() * 1000, + accountViewModel.account.zapAmountChoices.first() * 1000, poolOption.option, "", context, @@ -354,7 +355,7 @@ fun ZapVote( zappingProgress = it } }, - zapType = account.defaultZapType + zapType = accountViewModel.account.defaultZapType ) } } else { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt index 4b1839551..0a7d2d61f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt @@ -519,10 +519,8 @@ private fun ZapIcon( var wasZappedByLoggedInUser by remember { mutableStateOf(false) } val zapsState by baseNote.live().zaps.observeAsState() - val scope = rememberCoroutineScope() - LaunchedEffect(key1 = zapsState) { - scope.launch(Dispatchers.IO) { + launch(Dispatchers.IO) { zapsState?.note?.let { if (!wasZappedByLoggedInUser) { val newWasZapped = accountViewModel.calculateIfNoteWasZappedByAccount(it) @@ -559,17 +557,16 @@ private fun ZapAmountText( accountViewModel: AccountViewModel ) { val zapsState by baseNote.live().zaps.observeAsState() - val zappedNote = remember(zapsState) { zapsState?.note } ?: return - - val scope = rememberCoroutineScope() var zapAmountTxt by remember { mutableStateOf("") } LaunchedEffect(key1 = zapsState) { - scope.launch(Dispatchers.IO) { - val newZapAmount = showAmount(accountViewModel.calculateZapAmount(zappedNote)) - if (newZapAmount != zapAmountTxt) { - zapAmountTxt = newZapAmount + launch(Dispatchers.IO) { + zapsState?.note?.let { + val newZapAmount = showAmount(accountViewModel.calculateZapAmount(it)) + if (newZapAmount != zapAmountTxt) { + zapAmountTxt = newZapAmount + } } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt index 47e8cdf0c..2fff6f261 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt @@ -307,11 +307,13 @@ private fun ReportsTabHeader(baseUser: User) { var userReports by remember { mutableStateOf(0) } LaunchedEffect(key1 = userState) { - UserProfileReportsFeedFilter.user = baseUser - val newSize = UserProfileReportsFeedFilter.feed().size + launch(Dispatchers.IO) { + UserProfileReportsFeedFilter.user = baseUser + val newSize = UserProfileReportsFeedFilter.feed().size - if (newSize != userReports) { - userReports = newSize + if (newSize != userReports) { + userReports = newSize + } } } @@ -321,12 +323,19 @@ private fun ReportsTabHeader(baseUser: User) { @Composable private fun BookmarkTabHeader(baseUser: User) { val userState by baseUser.live().bookmarks.observeAsState() - val userBookmarks = remember(userState) { - val bookmarkList = userState?.user?.latestBookmarkList - (bookmarkList?.taggedEvents()?.count() ?: 0) + ( - bookmarkList?.taggedAddresses()?.count() - ?: 0 - ) + + var userBookmarks by remember { mutableStateOf(0) } + + LaunchedEffect(key1 = userState) { + launch(Dispatchers.IO) { + val bookmarkList = userState?.user?.latestBookmarkList + + val newBookmarks = (bookmarkList?.taggedEvents()?.count() ?: 0) + (bookmarkList?.taggedAddresses()?.count() ?: 0) + + if (newBookmarks != userBookmarks) { + userBookmarks = newBookmarks + } + } } Text(text = "$userBookmarks ${stringResource(R.string.bookmarks)}")