diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ServiceManager.kt b/app/src/main/java/com/vitorpamplona/amethyst/ServiceManager.kt index 87a443de6..114d88190 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ServiceManager.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ServiceManager.kt @@ -14,7 +14,6 @@ import com.vitorpamplona.amethyst.service.HttpClient import com.vitorpamplona.amethyst.service.NostrAccountDataSource import com.vitorpamplona.amethyst.service.NostrChannelDataSource import com.vitorpamplona.amethyst.service.NostrChatroomListDataSource -import com.vitorpamplona.amethyst.service.NostrGlobalDataSource import com.vitorpamplona.amethyst.service.NostrHomeDataSource import com.vitorpamplona.amethyst.service.NostrSingleChannelDataSource import com.vitorpamplona.amethyst.service.NostrSingleEventDataSource @@ -89,7 +88,6 @@ object ServiceManager { NostrChannelDataSource.stop() NostrChatroomListDataSource.stop() - NostrGlobalDataSource.stop() NostrSingleChannelDataSource.stop() NostrSingleEventDataSource.stop() NostrSingleUserDataSource.stop() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrGlobalDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrGlobalDataSource.kt deleted file mode 100644 index cec80961f..000000000 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrGlobalDataSource.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.vitorpamplona.amethyst.service - -import com.vitorpamplona.amethyst.service.model.AudioTrackEvent -import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent -import com.vitorpamplona.amethyst.service.model.HighlightEvent -import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent -import com.vitorpamplona.amethyst.service.model.PinListEvent -import com.vitorpamplona.amethyst.service.model.PollNoteEvent -import com.vitorpamplona.amethyst.service.model.TextNoteEvent -import com.vitorpamplona.amethyst.service.relays.FeedType -import com.vitorpamplona.amethyst.service.relays.JsonFilter -import com.vitorpamplona.amethyst.service.relays.TypedFilter - -object NostrGlobalDataSource : NostrDataSource("GlobalFeed") { - fun createGlobalFilter() = TypedFilter( - types = setOf(FeedType.GLOBAL), - filter = JsonFilter( - kinds = listOf(TextNoteEvent.kind, PollNoteEvent.kind, ChannelMessageEvent.kind, AudioTrackEvent.kind, PinListEvent.kind, LongTextNoteEvent.kind, HighlightEvent.kind), - limit = 200 - ) - ) - - val globalFeedChannel = requestNewChannel() - - override fun updateChannelFilters() { - globalFeedChannel.typedFilters = listOf(createGlobalFilter()).ifEmpty { null } - } -} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrHomeDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrHomeDataSource.kt index 4b2cc8356..0876cd519 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrHomeDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrHomeDataSource.kt @@ -51,13 +51,13 @@ object NostrHomeDataSource : NostrDataSource("HomeFeed") { } fun createFollowAccountsFilter(): TypedFilter { - val follows = account.selectedUsersFollowList(account.defaultHomeFollowList) ?: emptySet() + val follows = account.selectedUsersFollowList(account.defaultHomeFollowList) - val followKeys = follows.map { + val followKeys = follows?.map { it.substring(0, 6) } - val followSet = followKeys.plus(account.userProfile().pubkeyHex.substring(0, 6)) + val followSet = followKeys?.plus(account.userProfile().pubkeyHex.substring(0, 6)) return TypedFilter( types = setOf(FeedType.FOLLOWS), diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/GlobalFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/GlobalFeedFilter.kt deleted file mode 100644 index ebbe5cb63..000000000 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/GlobalFeedFilter.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.vitorpamplona.amethyst.ui.dal - -import com.vitorpamplona.amethyst.model.Account -import com.vitorpamplona.amethyst.model.LocalCache -import com.vitorpamplona.amethyst.model.Note -import com.vitorpamplona.amethyst.service.model.* - -class GlobalFeedFilter(val account: Account) : AdditiveFeedFilter() { - - override fun feedKey(): String { - return account.userProfile().pubkeyHex - } - - override fun feed(): List { - val notes = innerApplyFilter(LocalCache.notes.values) - val longFormNotes = innerApplyFilter(LocalCache.addressables.values) - - return sort(notes + longFormNotes) - } - - override fun applyFilter(collection: Set): Set { - return innerApplyFilter(collection) - } - - private fun innerApplyFilter(collection: Collection): Set { - val followChannels = account.followingChannels - val followUsers = account.followingKeySet() - val now = System.currentTimeMillis() / 1000 - - return collection - .asSequence() - .filter { - (it.event is BaseTextNoteEvent || it.event is AudioTrackEvent) && it.replyTo.isNullOrEmpty() - } - .filter { - val channel = it.channelHex() - // does not show events already in the public chat list - (channel == null || channel !in followChannels) && - // does not show people the user already follows - (it.author?.pubkeyHex !in followUsers) - } - .filter { account.isAcceptable(it) } - .filter { - // Do not show notes with the creation time exceeding the current time, as they will always stay at the top of the global feed, which is cheating. - it.createdAt()!! <= now - } - .toSet() - } - - override fun sort(collection: Set): List { - return collection.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed() - } -} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HomeConversationsFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HomeConversationsFeedFilter.kt index 32ca8aa3a..e4861cfe6 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HomeConversationsFeedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HomeConversationsFeedFilter.kt @@ -1,6 +1,7 @@ package com.vitorpamplona.amethyst.ui.dal import com.vitorpamplona.amethyst.model.Account +import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent @@ -23,6 +24,8 @@ class HomeConversationsFeedFilter(val account: Account) : AdditiveFeedFilter): Set { + val isGlobal = account.defaultHomeFollowList == GLOBAL_FOLLOWS + val followingKeySet = account.selectedUsersFollowList(account.defaultHomeFollowList) ?: emptySet() val followingTagSet = account.selectedTagsFollowList(account.defaultHomeFollowList) ?: emptySet() @@ -32,7 +35,7 @@ class HomeConversationsFeedFilter(val account: Account) : AdditiveFeedFilter): Set { checkNotInMainThread() + val isGlobal = account.defaultHomeFollowList == GLOBAL_FOLLOWS + val followingKeySet = account.selectedUsersFollowList(account.defaultHomeFollowList) ?: emptySet() val followingTagSet = account.selectedTagsFollowList(account.defaultHomeFollowList) ?: emptySet() @@ -39,7 +42,7 @@ class HomeLiveActivitiesFeedFilter(val account: Account) : AdditiveFeedFilter val noteEvent = it.event (noteEvent is LiveActivitiesEvent && noteEvent.createdAt > twoHrs && noteEvent.status() == "live" && OnlineChecker.isOnline(noteEvent.streaming())) && - (it.author?.pubkeyHex in followingKeySet || (noteEvent.isTaggedHashes(followingTagSet))) && + (isGlobal || it.author?.pubkeyHex in followingKeySet || noteEvent.isTaggedHashes(followingTagSet)) && // && account.isAcceptable(it) // This filter follows only. No need to check if acceptable it.author?.let { !account.isHidden(it.pubkeyHex) } ?: true } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HomeNewThreadFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HomeNewThreadFeedFilter.kt index e87eb451c..d64dea76a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HomeNewThreadFeedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HomeNewThreadFeedFilter.kt @@ -1,6 +1,7 @@ package com.vitorpamplona.amethyst.ui.dal import com.vitorpamplona.amethyst.model.Account +import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.service.model.AudioTrackEvent @@ -29,6 +30,8 @@ class HomeNewThreadFeedFilter(val account: Account) : AdditiveFeedFilter() } private fun innerApplyFilter(collection: Collection): Set { + val isGlobal = account.defaultHomeFollowList == GLOBAL_FOLLOWS + val followingKeySet = account.selectedUsersFollowList(account.defaultHomeFollowList) ?: emptySet() val followingTagSet = account.selectedTagsFollowList(account.defaultHomeFollowList) ?: emptySet() @@ -40,7 +43,7 @@ class HomeNewThreadFeedFilter(val account: Account) : AdditiveFeedFilter() .filter { it -> val noteEvent = it.event (noteEvent is TextNoteEvent || noteEvent is RepostEvent || noteEvent is GenericRepostEvent || noteEvent is LongTextNoteEvent || noteEvent is PollNoteEvent || noteEvent is HighlightEvent || noteEvent is AudioTrackEvent) && - (it.author?.pubkeyHex in followingKeySet || (noteEvent.isTaggedHashes(followingTagSet))) && + (isGlobal || it.author?.pubkeyHex in followingKeySet || noteEvent.isTaggedHashes(followingTagSet)) && // && account.isAcceptable(it) // This filter follows only. No need to check if acceptable it.author?.let { !account.isHidden(it.pubkeyHex) } ?: true && ((it.event?.createdAt() ?: 0) < oneMinuteInTheFuture) && 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 19b8e36f6..1d2e5b942 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 @@ -48,7 +48,6 @@ val bottomNavigationItems = listOf( Route.Home, Route.Message, Route.Video, - Route.Search, Route.Notification ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt index 1d8053a3a..e3de2e004 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt @@ -12,7 +12,6 @@ import androidx.navigation.compose.composable import com.vitorpamplona.amethyst.ui.note.UserReactionsViewModel import com.vitorpamplona.amethyst.ui.screen.NostrChatroomListKnownFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrChatroomListNewFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.NostrGlobalFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrHomeFeedLiveActivitiesViewModel import com.vitorpamplona.amethyst.ui.screen.NostrHomeFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrHomeRepliesFeedViewModel @@ -42,7 +41,6 @@ fun AppNavigation( liveActivitiesViewModel: NostrHomeFeedLiveActivitiesViewModel, knownFeedViewModel: NostrChatroomListKnownFeedViewModel, newFeedViewModel: NostrChatroomListNewFeedViewModel, - searchFeedViewModel: NostrGlobalFeedViewModel, videoFeedViewModel: NostrVideoFeedViewModel, notifFeedViewModel: NotificationViewModel, userReactionsStatsModel: UserReactionsViewModel, @@ -111,7 +109,6 @@ fun AppNavigation( Route.Search.let { route -> composable(route.route, route.arguments, content = { SearchScreen( - searchFeedViewModel = searchFeedViewModel, accountViewModel = accountViewModel, nav = nav ) 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 b3300aea6..dfcc348b5 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 @@ -48,7 +48,6 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.map import androidx.lifecycle.viewModelScope -import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavBackStackEntry import androidx.navigation.NavHostController import coil.Coil @@ -61,7 +60,6 @@ import com.vitorpamplona.amethyst.service.NostrAccountDataSource import com.vitorpamplona.amethyst.service.NostrChannelDataSource import com.vitorpamplona.amethyst.service.NostrChatroomDataSource import com.vitorpamplona.amethyst.service.NostrChatroomListDataSource -import com.vitorpamplona.amethyst.service.NostrGlobalDataSource import com.vitorpamplona.amethyst.service.NostrHashtagDataSource import com.vitorpamplona.amethyst.service.NostrHomeDataSource import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource @@ -74,9 +72,7 @@ import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.service.model.PeopleListEvent import com.vitorpamplona.amethyst.service.relays.Client import com.vitorpamplona.amethyst.service.relays.RelayPool -import com.vitorpamplona.amethyst.ui.actions.NewRelayListView import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy -import com.vitorpamplona.amethyst.ui.screen.RelayPoolViewModel import com.vitorpamplona.amethyst.ui.screen.equalImmutableLists import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.SpinnerSelectionDialog @@ -158,7 +154,7 @@ fun HomeTopBar(followLists: FollowListViewModel, scaffoldState: ScaffoldState, a FollowList( followLists, list, - false + true ) { listName -> accountViewModel.account.changeDefaultHomeFollowList(listName) } @@ -194,14 +190,6 @@ fun MainTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewModel, fun GenericTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewModel, nav: (String) -> Unit, content: @Composable (AccountViewModel) -> Unit) { val coroutineScope = rememberCoroutineScope() - var wantsToEditRelays by remember { - mutableStateOf(false) - } - - if (wantsToEditRelays) { - NewRelayListView({ wantsToEditRelays = false }, accountViewModel, nav = nav) - } - Column(modifier = Modifier.height(50.dp)) { TopAppBar( elevation = 0.dp, @@ -222,10 +210,6 @@ fun GenericTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewMod ) { content(accountViewModel) } - - RelayStatus( - { wantsToEditRelays = true } - ) } } }, @@ -237,7 +221,9 @@ fun GenericTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewMod } }, actions = { - RelayIcon { wantsToEditRelays = true } + SearchIcon() { + nav(Route.Search.route) + } } ) Divider(thickness = 0.25.dp) @@ -245,80 +231,14 @@ fun GenericTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewMod } @Composable -private fun RelayStatus( - onClick: () -> Unit -) { - val relayViewModel: RelayPoolViewModel = viewModel { RelayPoolViewModel() } - Column( - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight(), - horizontalAlignment = Alignment.End - ) { - Row( - modifier = Modifier.fillMaxHeight(), - verticalAlignment = Alignment.CenterVertically - ) { - RelayStatus(relayViewModel, onClick) - } - } -} - -@Composable -private fun RelayStatus( - relayViewModel: RelayPoolViewModel, - onClick: () -> Unit -) { - val connectedRelaysLiveData = relayViewModel.connectedRelaysLiveData.observeAsState() - val availableRelaysLiveData = relayViewModel.availableRelaysLiveData.observeAsState() - - val connectedRelaysText by remember(connectedRelaysLiveData, availableRelaysLiveData) { - derivedStateOf { - "${connectedRelaysLiveData.value ?: "--"}/${availableRelaysLiveData.value ?: "--"}" - } - } - - val isConnected by remember(connectedRelaysLiveData) { - derivedStateOf { - (connectedRelaysLiveData.value ?: 0) > 0 - } - } - - RenderRelayStatus(connectedRelaysText, isConnected, onClick) -} - -@Composable -private fun RenderRelayStatus( - connectedRelaysText: String, - isConnected: Boolean, - onClick: () -> Unit -) { - Text( - text = connectedRelaysText, - color = if (isConnected) { - MaterialTheme.colors.onSurface.copy( - alpha = 0.32f - ) - } else { - Color.Red - }, - style = MaterialTheme.typography.subtitle1, - modifier = Modifier.clickable( - onClick = onClick - ) - ) -} - -@Composable -private fun RelayIcon(onClick: () -> Unit) { +private fun SearchIcon(onClick: () -> Unit) { IconButton( - onClick = onClick, - modifier = Modifier + onClick = onClick ) { Icon( - painter = painterResource(R.drawable.relays), - null, - modifier = Modifier.size(24.dp), + painter = painterResource(R.drawable.ic_search), + contentDescription = null, + modifier = Modifier.size(22.dp), tint = Color.Unspecified ) } @@ -534,7 +454,6 @@ fun AmethystIcon() { NostrChannelDataSource.printCounter() NostrChatroomDataSource.printCounter() NostrChatroomListDataSource.printCounter() - NostrGlobalDataSource.printCounter() NostrHashtagDataSource.printCounter() NostrHomeDataSource.printCounter() NostrSearchEventOrUserDataSource.printCounter() 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 2ada7d214..e44231d86 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 @@ -34,6 +34,7 @@ import androidx.compose.material.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf @@ -52,6 +53,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel import coil.compose.AsyncImage import com.vitorpamplona.amethyst.BuildConfig import com.vitorpamplona.amethyst.LocalPreferences @@ -60,13 +62,17 @@ import com.vitorpamplona.amethyst.ServiceManager import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.HttpClient +import com.vitorpamplona.amethyst.ui.actions.NewRelayListView import com.vitorpamplona.amethyst.ui.actions.toImmutableListOfLists import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy +import com.vitorpamplona.amethyst.ui.screen.RelayPoolViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountBackupDialog import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.ConnectOrbotDialog import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer +import com.vitorpamplona.amethyst.ui.theme.Size16dp +import com.vitorpamplona.amethyst.ui.theme.placeholderText import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -301,6 +307,12 @@ fun ListContent( var conectOrbotDialogOpen by remember { mutableStateOf(false) } var proxyPort = remember { mutableStateOf(account.proxyPort.toString()) } + val relayViewModel: RelayPoolViewModel = viewModel { RelayPoolViewModel() } + + var wantsToEditRelays by remember { + mutableStateOf(false) + } + Column( modifier = modifier .fillMaxHeight() @@ -324,6 +336,16 @@ fun ListContent( route = Route.Bookmarks.route ) + IconRowRelays( + relayViewModel = relayViewModel, + onClick = { + coroutineScope.launch { + scaffoldState.drawerState.close() + } + wantsToEditRelays = true + } + ) + NavigationRow( title = stringResource(R.string.security_filters), icon = Route.BlockedUsers.icon, @@ -431,6 +453,10 @@ fun ListContent( } ) } + + if (wantsToEditRelays) { + NewRelayListView({ wantsToEditRelays = false }, accountViewModel, nav = nav) + } } private fun enableTor( @@ -446,6 +472,44 @@ private fun enableTor( ServiceManager.start(context) } +@Composable +private fun RelayStatus( + relayViewModel: RelayPoolViewModel +) { + val connectedRelaysLiveData = relayViewModel.connectedRelaysLiveData.observeAsState() + val availableRelaysLiveData = relayViewModel.availableRelaysLiveData.observeAsState() + + val connectedRelaysText by remember(connectedRelaysLiveData, availableRelaysLiveData) { + derivedStateOf { + "${connectedRelaysLiveData.value ?: "--"}/${availableRelaysLiveData.value ?: "--"}" + } + } + + val isConnected by remember(connectedRelaysLiveData) { + derivedStateOf { + (connectedRelaysLiveData.value ?: 0) > 0 + } + } + + RenderRelayStatus(connectedRelaysText, isConnected) +} + +@Composable +private fun RenderRelayStatus( + connectedRelaysText: String, + isConnected: Boolean +) { + Text( + text = connectedRelaysText, + color = if (isConnected) { + MaterialTheme.colors.placeholderText + } else { + Color.Red + }, + style = MaterialTheme.typography.subtitle1 + ) +} + @Composable fun NavigationRow( title: String, @@ -496,6 +560,39 @@ fun IconRow(title: String, icon: Int, tint: Color, onClick: () -> Unit, onLongCl } } +@Composable +fun IconRowRelays(relayViewModel: RelayPoolViewModel, onClick: () -> Unit) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { onClick() } + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 15.dp, horizontal = 25.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + painter = painterResource(R.drawable.relays), + null, + modifier = Modifier.size(22.dp), + tint = MaterialTheme.colors.onSurface + ) + + Text( + modifier = Modifier.padding(start = 16.dp), + text = stringResource(id = R.string.relays), + fontSize = 18.sp + ) + + Spacer(modifier = Modifier.width(Size16dp)) + + RelayStatus(relayViewModel = relayViewModel) + } + } +} + @Composable fun BottomContent(user: User, scaffoldState: ScaffoldState, nav: (String) -> Unit) { val coroutineScope = rememberCoroutineScope() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt index 2e334a9f4..b4b7b086d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt @@ -42,9 +42,14 @@ sealed class Route( hasNewItems = { accountViewModel, newNotes -> HomeLatestItem.hasNewItems(accountViewModel, newNotes) } ) + object Global : Route( + route = "Global", + icon = R.drawable.ic_globe + ) + object Search : Route( route = "Search", - icon = R.drawable.ic_globe + icon = R.drawable.ic_search ) object Video : Route( 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 0cf2c9caa..8e1812a5a 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 @@ -22,7 +22,6 @@ import com.vitorpamplona.amethyst.ui.dal.ChatroomFeedFilter import com.vitorpamplona.amethyst.ui.dal.ChatroomListKnownFeedFilter import com.vitorpamplona.amethyst.ui.dal.ChatroomListNewFeedFilter import com.vitorpamplona.amethyst.ui.dal.FeedFilter -import com.vitorpamplona.amethyst.ui.dal.GlobalFeedFilter import com.vitorpamplona.amethyst.ui.dal.HashtagFeedFilter import com.vitorpamplona.amethyst.ui.dal.HomeConversationsFeedFilter import com.vitorpamplona.amethyst.ui.dal.HomeLiveActivitiesFeedFilter @@ -59,13 +58,6 @@ class NostrChatroomFeedViewModel(val user: User, val account: Account) : FeedVie } } -class NostrGlobalFeedViewModel(val account: Account) : FeedViewModel(GlobalFeedFilter(account)) { - class Factory(val account: Account) : ViewModelProvider.Factory { - override fun create(modelClass: Class): NostrGlobalFeedViewModel { - return NostrGlobalFeedViewModel(account) as NostrGlobalFeedViewModel - } - } -} class NostrVideoFeedViewModel(val account: Account) : FeedViewModel(VideoFeedFilter(account)) { class Factory(val account: Account) : ViewModelProvider.Factory { override fun create(modelClass: Class): NostrVideoFeedViewModel { @@ -201,8 +193,8 @@ abstract class FeedViewModel(val localFilter: FeedFilter) : ViewModel(), I fun refreshSuspended() { checkNotInMainThread() - val notes = localFilter.loadTop().toImmutableList() lastFeedKey = localFilter.feedKey() + val notes = localFilter.loadTop().toImmutableList() val oldNotesState = _feedContent.value if (oldNotesState is FeedState.Loaded) { @@ -231,16 +223,14 @@ abstract class FeedViewModel(val localFilter: FeedFilter) : ViewModel(), I fun refreshFromOldState(newItems: Set) { val oldNotesState = _feedContent.value - if (localFilter is AdditiveFeedFilter) { + if (localFilter is AdditiveFeedFilter && lastFeedKey != localFilter.feedKey()) { if (oldNotesState is FeedState.Loaded) { val newList = localFilter.updateListWith(oldNotesState.feed.value, newItems.toSet()).toImmutableList() - lastFeedKey = localFilter.feedKey() if (!equalImmutableLists(newList, oldNotesState.feed.value)) { updateFeed(newList) } } else if (oldNotesState is FeedState.Empty) { val newList = localFilter.updateListWith(emptyList(), newItems.toSet()).toImmutableList() - lastFeedKey = localFilter.feedKey() if (newList.isNotEmpty()) { updateFeed(newList) } 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 ed52746d3..fa0536403 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 @@ -38,13 +38,11 @@ import com.vitorpamplona.amethyst.ui.navigation.AppNavigation import com.vitorpamplona.amethyst.ui.navigation.AppTopBar import com.vitorpamplona.amethyst.ui.navigation.DrawerContent import com.vitorpamplona.amethyst.ui.navigation.Route -import com.vitorpamplona.amethyst.ui.navigation.currentRoute import com.vitorpamplona.amethyst.ui.note.UserReactionsViewModel import com.vitorpamplona.amethyst.ui.screen.AccountState import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel import com.vitorpamplona.amethyst.ui.screen.NostrChatroomListKnownFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrChatroomListNewFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.NostrGlobalFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrHomeFeedLiveActivitiesViewModel import com.vitorpamplona.amethyst.ui.screen.NostrHomeFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrHomeRepliesFeedViewModel @@ -95,11 +93,6 @@ fun MainScreen(accountViewModel: AccountViewModel, accountStateViewModel: Accoun factory = NostrHomeFeedLiveActivitiesViewModel.Factory(accountViewModel.account) ) - val searchFeedViewModel: NostrGlobalFeedViewModel = viewModel( - key = accountViewModel.userProfile().pubkeyHex + "NostrGlobalFeedViewModel", - factory = NostrGlobalFeedViewModel.Factory(accountViewModel.account) - ) - val videoFeedViewModel: NostrVideoFeedViewModel = viewModel( key = accountViewModel.userProfile().pubkeyHex + "NostrVideoFeedViewModel", factory = NostrVideoFeedViewModel.Factory(accountViewModel.account) @@ -140,9 +133,6 @@ fun MainScreen(accountViewModel: AccountViewModel, accountStateViewModel: Accoun homeFeedViewModel.sendToTop() repliesFeedViewModel.sendToTop() } - Route.Search.base -> { - searchFeedViewModel.sendToTop() - } Route.Video.base -> { videoFeedViewModel.sendToTop() } @@ -193,7 +183,6 @@ fun MainScreen(accountViewModel: AccountViewModel, accountStateViewModel: Accoun liveActivitiesViewModel, knownFeedViewModel, newFeedViewModel, - searchFeedViewModel, videoFeedViewModel, notifFeedViewModel, userReactionsStatsModel, 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 79a5cbcab..9a17d1a7f 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 @@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size @@ -56,7 +57,6 @@ import com.vitorpamplona.amethyst.model.Channel import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User -import com.vitorpamplona.amethyst.service.NostrGlobalDataSource import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.ui.components.BundledUpdate @@ -66,9 +66,6 @@ import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture import com.vitorpamplona.amethyst.ui.note.NoteCompose import com.vitorpamplona.amethyst.ui.note.UserCompose import com.vitorpamplona.amethyst.ui.note.UsernameDisplay -import com.vitorpamplona.amethyst.ui.screen.NostrGlobalFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.RefresheableFeedView -import com.vitorpamplona.amethyst.ui.screen.ScrollStateKeys import com.vitorpamplona.amethyst.ui.theme.placeholderText import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.FlowPreview @@ -86,7 +83,6 @@ import kotlinx.coroutines.channels.Channel as CoroutineChannel @Composable fun SearchScreen( - searchFeedViewModel: NostrGlobalFeedViewModel, accountViewModel: AccountViewModel, nav: (String) -> Unit ) { @@ -97,33 +93,29 @@ fun SearchScreen( ) ) - SearchScreen(searchFeedViewModel, searchBarViewModel, accountViewModel, nav) + SearchScreen(searchBarViewModel, accountViewModel, nav) } @Composable fun SearchScreen( - searchFeedViewModel: NostrGlobalFeedViewModel, searchBarViewModel: SearchBarViewModel, accountViewModel: AccountViewModel, nav: (String) -> Unit ) { val lifeCycleOwner = LocalLifecycleOwner.current - WatchAccountForSearchScreen(searchFeedViewModel, accountViewModel) + WatchAccountForSearchScreen(accountViewModel) DisposableEffect(accountViewModel) { val observer = LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_RESUME) { - println("Global Start") - NostrGlobalDataSource.start() + println("Search Start") NostrSearchEventOrUserDataSource.start() - searchFeedViewModel.invalidateData() } if (event == Lifecycle.Event.ON_PAUSE) { - println("Global Stop") + println("Search Stop") NostrSearchEventOrUserDataSource.clear() NostrSearchEventOrUserDataSource.stop() - NostrGlobalDataSource.stop() } } @@ -133,29 +125,19 @@ fun SearchScreen( } } - Column(Modifier.fillMaxHeight()) { - Column( - modifier = Modifier.padding(vertical = 0.dp) - ) { - SearchBar(searchBarViewModel, accountViewModel, nav) - RefresheableFeedView( - searchFeedViewModel, - null, - scrollStateKey = ScrollStateKeys.GLOBAL_SCREEN, - accountViewModel = accountViewModel, - nav = nav - ) - } + val listState = rememberLazyListState() + + Column(Modifier.fillMaxSize()) { + SearchBar(searchBarViewModel, listState) + DisplaySearchResults(searchBarViewModel, listState, nav, accountViewModel) } } @Composable -fun WatchAccountForSearchScreen(searchFeedViewModel: NostrGlobalFeedViewModel, accountViewModel: AccountViewModel) { +fun WatchAccountForSearchScreen(accountViewModel: AccountViewModel) { LaunchedEffect(accountViewModel) { launch(Dispatchers.IO) { - NostrGlobalDataSource.resetFilters() NostrSearchEventOrUserDataSource.start() - searchFeedViewModel.invalidateData(true) } } } @@ -228,11 +210,9 @@ class SearchBarViewModel(val account: Account) : ViewModel() { @Composable private fun SearchBar( searchBarViewModel: SearchBarViewModel, - accountViewModel: AccountViewModel, - nav: (String) -> Unit + listState: LazyListState ) { val scope = rememberCoroutineScope() - val listState = rememberLazyListState() // Create a channel for processing search queries. val searchTextChanges = remember { @@ -283,8 +263,6 @@ private fun SearchBar( searchTextChanges.trySend(it) } } - - DisplaySearchResults(listState, searchBarViewModel, nav, accountViewModel) } @Composable @@ -352,8 +330,8 @@ private fun SearchTextField( @Composable private fun DisplaySearchResults( - listState: LazyListState, searchBarViewModel: SearchBarViewModel, + listState: LazyListState, nav: (String) -> Unit, accountViewModel: AccountViewModel ) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 87d76e5c7..b44705b8b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -468,4 +468,6 @@ Livestream Ended Logging out deletes all your local information. Make sure to have your private keys backed up to avoid losing your account. Do you want to continue? Followed Tags + + Relays