Going through tabs...
This commit is contained in:
@@ -3,77 +3,54 @@ package ac.aux.compose.ui.composable
|
||||
import ac.aux.compose.repository.NostrRepository
|
||||
import ac.aux.compose.repository.SearchRepository
|
||||
import ac.aux.compose.ui.theme.AuxTheme
|
||||
import ac.aux.compose.ui.view.model.FeedListViewModel
|
||||
import ac.aux.compose.ui.view.model.SearchViewModel
|
||||
import ac.aux.compose.ui.view.state.SearchUIState
|
||||
import androidx.compose.foundation.clickable
|
||||
import ac.aux.compose.ui.view.model.SearchResultType
|
||||
import ac.aux.compose.ui.view.model.SearchResultViewModel
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.input.TextFieldState
|
||||
import androidx.compose.foundation.text.input.rememberTextFieldState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBackIosNew
|
||||
import androidx.compose.material.icons.filled.DocumentScanner
|
||||
import androidx.compose.material.icons.filled.ManageSearch
|
||||
import androidx.compose.material.icons.filled.Person
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material.icons.filled.SettingsInputComponent
|
||||
import androidx.compose.material.icons.filled.Tag
|
||||
import androidx.compose.material.icons.filled.TravelExplore
|
||||
import androidx.compose.material.icons.filled.Tune
|
||||
import androidx.compose.material.icons.filled.YoutubeSearchedFor
|
||||
import androidx.compose.material3.AppBarWithSearch
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonColors
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.ExpandedFullScreenSearchBar
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.ListItemDefaults
|
||||
import androidx.compose.material3.IconButtonDefaults
|
||||
import androidx.compose.material3.LoadingIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.PrimaryScrollableTabRow
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SearchBar
|
||||
import androidx.compose.material3.SearchBarDefaults
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Tab
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.TopSearchBar
|
||||
import androidx.compose.material3.rememberSearchBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.semantics.isTraversalGroup
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.semantics.traversalIndex
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun SearchResultScreen(
|
||||
searchQuery: String,
|
||||
@@ -81,9 +58,9 @@ fun SearchResultScreen(
|
||||
searchRepository: SearchRepository,
|
||||
onNavigateBack: () -> Unit,
|
||||
) {
|
||||
val searchViewModel: SearchViewModel = viewModel(
|
||||
factory = SearchViewModel.factory(
|
||||
initialSearchUIState = SearchUIState.Prompt,
|
||||
val searchViewModel: SearchResultViewModel = viewModel(
|
||||
factory = SearchResultViewModel.factory(
|
||||
initialSearchResultType = SearchResultType.Posts,
|
||||
nostrRepository = nostrRepository,
|
||||
searchRepository = searchRepository
|
||||
),
|
||||
@@ -146,7 +123,6 @@ fun SearchResultScreen(
|
||||
searchViewModel.performSearch(
|
||||
textFieldState.text.toString()
|
||||
)
|
||||
expanded = false
|
||||
},
|
||||
enabled = false,
|
||||
expanded = expanded,
|
||||
@@ -155,6 +131,10 @@ fun SearchResultScreen(
|
||||
leadingIcon = {
|
||||
if (expanded) {
|
||||
IconButton(
|
||||
enabled = true,
|
||||
colors = IconButtonDefaults.iconButtonColors(
|
||||
contentColor = MaterialTheme.colorScheme.primary
|
||||
),
|
||||
onClick = {
|
||||
// TODO: onNavigateBack
|
||||
onNavigateBack.invoke()
|
||||
@@ -175,6 +155,9 @@ fun SearchResultScreen(
|
||||
},
|
||||
trailingIcon = {
|
||||
IconButton(
|
||||
colors = IconButtonDefaults.iconButtonColors(
|
||||
contentColor = MaterialTheme.colorScheme.primary
|
||||
),
|
||||
onClick = {
|
||||
// TODO: Adjust search
|
||||
}
|
||||
@@ -193,49 +176,39 @@ fun SearchResultScreen(
|
||||
},
|
||||
windowInsets = TopAppBarDefaults.windowInsets
|
||||
) {
|
||||
// TODO: Tabs
|
||||
LazyColumn {
|
||||
itemsIndexed(searchResults) { index, item ->
|
||||
ListItem(
|
||||
headlineContent = { Text(item) },
|
||||
supportingContent = {
|
||||
if (index == 0) {
|
||||
Text(
|
||||
text = "Search"
|
||||
)
|
||||
} else if (index == 1) {
|
||||
Text(
|
||||
text = "Search hashtags"
|
||||
)
|
||||
} else {
|
||||
Text("Profile")
|
||||
}
|
||||
PrimaryScrollableTabRow(
|
||||
modifier = Modifier,
|
||||
edgePadding = 10.dp,
|
||||
selectedTabIndex = searchViewModel.searchResultType.ordinal,
|
||||
) {
|
||||
SearchResultType.entries.forEach { searchResultType ->
|
||||
Tab(
|
||||
selected = searchViewModel.searchResultType == searchResultType,
|
||||
onClick = {
|
||||
searchViewModel.updateSearchResultType(searchResultType)
|
||||
},
|
||||
leadingContent = {
|
||||
if (index == 0) {
|
||||
Icon(
|
||||
Icons.Default.TravelExplore,
|
||||
contentDescription = "Search globally"
|
||||
)
|
||||
} else if (index == 1) {
|
||||
Icon(
|
||||
Icons.Default.Tag,
|
||||
contentDescription = "Search hashtags"
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
Icons.Default.Person,
|
||||
contentDescription = "Person"
|
||||
)
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
.clickable {
|
||||
text = {
|
||||
Text(
|
||||
text = searchResultType.name,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.fillMaxWidth(),
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp)
|
||||
) {
|
||||
item {
|
||||
Spacer(
|
||||
modifier = Modifier.height(50.dp)
|
||||
)
|
||||
|
||||
)
|
||||
LoadingIndicator()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import androidx.compose.foundation.text.input.rememberTextFieldState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBackIosNew
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.ManageSearch
|
||||
import androidx.compose.material.icons.filled.Person
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
@@ -59,6 +60,7 @@ import androidx.compose.material3.TopSearchBar
|
||||
import androidx.compose.material3.rememberSearchBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
@@ -118,35 +120,38 @@ fun SearchScreen(
|
||||
"Zinfandel",
|
||||
)
|
||||
|
||||
val recentSearches = listOf(
|
||||
"Alligator",
|
||||
"Baboon",
|
||||
"Camel",
|
||||
"Deer",
|
||||
"Earthworm",
|
||||
"Falcon",
|
||||
"Gazelle",
|
||||
"Hamster",
|
||||
"Iguana",
|
||||
"Jellyfish",
|
||||
"Koala",
|
||||
"Leopard",
|
||||
"Meerkat",
|
||||
"Nilgai",
|
||||
"Octopus",
|
||||
"Panda",
|
||||
"Quokka",
|
||||
"Panda",
|
||||
"Rabbit",
|
||||
"Scorpion",
|
||||
"Tarantula",
|
||||
"Uakari",
|
||||
"Viper",
|
||||
"Walrus",
|
||||
"Xenops",
|
||||
"Yak",
|
||||
"Zebra",
|
||||
)
|
||||
val recentSearches = remember {
|
||||
mutableStateListOf(
|
||||
"Alligator",
|
||||
"Baboon",
|
||||
"Camel",
|
||||
"Deer",
|
||||
"Earthworm",
|
||||
"Falcon",
|
||||
"Gazelle",
|
||||
"Hamster",
|
||||
"Iguana",
|
||||
"Jellyfish",
|
||||
"Koala",
|
||||
"Leopard",
|
||||
"Meerkat",
|
||||
"Nilgai",
|
||||
"Octopus",
|
||||
"Panda",
|
||||
"Quokka",
|
||||
"Panda",
|
||||
"Rabbit",
|
||||
"Scorpion",
|
||||
"Tarantula",
|
||||
"Uakari",
|
||||
"Viper",
|
||||
"Walrus",
|
||||
"Xenops",
|
||||
"Yak",
|
||||
"Zebra",
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
val profileSearches = listOf(
|
||||
"Afghanistan",
|
||||
@@ -177,7 +182,7 @@ fun SearchScreen(
|
||||
)
|
||||
val textFieldState = rememberTextFieldState()
|
||||
// Controls expansion state of the search bar
|
||||
var expanded by rememberSaveable { mutableStateOf(false) }
|
||||
var expanded by rememberSaveable { mutableStateOf(true) }
|
||||
|
||||
Scaffold { innerPadding ->
|
||||
Column(
|
||||
@@ -242,68 +247,101 @@ fun SearchScreen(
|
||||
if (textFieldState.text.isNotBlank()) {
|
||||
// Display search results in a scrollable column
|
||||
LazyColumn {
|
||||
item {
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
Text(
|
||||
textFieldState.text.toString()
|
||||
)
|
||||
},
|
||||
supportingContent = {
|
||||
Text(
|
||||
text = "Search"
|
||||
)
|
||||
},
|
||||
leadingContent = {
|
||||
IconButton(
|
||||
onClick = {
|
||||
onNavigateToSearchResult.invoke(
|
||||
SearchResultRoute(
|
||||
textFieldState.text.toString()
|
||||
)
|
||||
)
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.TravelExplore,
|
||||
contentDescription = "Search globally"
|
||||
)
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
.clickable {
|
||||
onNavigateToSearchResult.invoke(
|
||||
SearchResultRoute(
|
||||
textFieldState.text.toString()
|
||||
)
|
||||
)
|
||||
}
|
||||
.fillMaxWidth(),
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
ListItem(
|
||||
headlineContent = { Text(textFieldState.text.toString()) },
|
||||
supportingContent = {
|
||||
Text(
|
||||
text = "Search hashtags"
|
||||
)
|
||||
},
|
||||
leadingContent = {
|
||||
IconButton(
|
||||
onClick = {
|
||||
onNavigateToSearchResult.invoke(
|
||||
SearchResultRoute(
|
||||
query = "#${textFieldState.text.toString().replace("#","")}"
|
||||
)
|
||||
)
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Tag,
|
||||
contentDescription = "Search hashtags"
|
||||
)
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
.clickable {
|
||||
onNavigateToSearchResult.invoke(
|
||||
SearchResultRoute(
|
||||
"#${textFieldState.text.toString().replaceFirst("#","")}"
|
||||
)
|
||||
)
|
||||
}
|
||||
.fillMaxWidth(),
|
||||
|
||||
)
|
||||
}
|
||||
itemsIndexed(searchResults) { index, item ->
|
||||
ListItem(
|
||||
headlineContent = { Text(item) },
|
||||
supportingContent = {
|
||||
if (index == 0) {
|
||||
Text(
|
||||
text = "Search"
|
||||
)
|
||||
} else if (index == 1) {
|
||||
Text(
|
||||
text = "Search hashtags"
|
||||
)
|
||||
} else {
|
||||
Text("Profile")
|
||||
}
|
||||
Text("Profile")
|
||||
},
|
||||
leadingContent = {
|
||||
if (index == 0) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
onNavigateToSearchResult.invoke(
|
||||
SearchResultRoute(
|
||||
textFieldState.text.toString()
|
||||
)
|
||||
)
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.TravelExplore,
|
||||
contentDescription = "Search globally"
|
||||
)
|
||||
}
|
||||
|
||||
} else if (index == 1) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
onNavigateToSearchResult.invoke(
|
||||
SearchResultRoute(
|
||||
query = "#${textFieldState.text.toString().replace("#","")}"
|
||||
)
|
||||
)
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Tag,
|
||||
contentDescription = "Search hashtags"
|
||||
)
|
||||
}
|
||||
|
||||
} else {
|
||||
IconButton(
|
||||
onClick = {
|
||||
onNavigateToProfile.invoke(
|
||||
ImplementationPendingRoute("Profile result")
|
||||
)
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Person,
|
||||
contentDescription = "Person"
|
||||
IconButton(
|
||||
onClick = {
|
||||
onNavigateToProfile.invoke(
|
||||
ImplementationPendingRoute("Profile result")
|
||||
)
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Person,
|
||||
contentDescription = "Person"
|
||||
)
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
@@ -316,7 +354,7 @@ fun SearchScreen(
|
||||
}
|
||||
.fillMaxWidth(),
|
||||
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -398,6 +436,18 @@ fun SearchScreen(
|
||||
)
|
||||
}
|
||||
.fillMaxWidth(),
|
||||
trailingContent = {
|
||||
IconButton(
|
||||
onClick = {
|
||||
recentSearches.removeAt(index)
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Close,
|
||||
contentDescription = "Remove search term"
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LargeTopAppBar
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.PrimaryScrollableTabRow
|
||||
import androidx.compose.material3.PrimaryTabRow
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Surface
|
||||
@@ -46,6 +47,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
@@ -169,22 +171,23 @@ fun MetadataEventDetail(
|
||||
}
|
||||
|
||||
item {
|
||||
val destinations = arrayOf("Notes", "Follows", "Following")
|
||||
val navController = rememberNavController()
|
||||
val destinations = arrayOf("Notes", "Replies", "Articles", "Follows", "Following", "Photos", "Videos")
|
||||
|
||||
val startDestination = 0
|
||||
val selectedDestination by rememberSaveable {
|
||||
var selectedDestination by rememberSaveable {
|
||||
mutableIntStateOf(startDestination)
|
||||
}
|
||||
|
||||
PrimaryTabRow(
|
||||
PrimaryScrollableTabRow(
|
||||
modifier = Modifier.padding(10.dp),
|
||||
edgePadding = 10.dp,
|
||||
selectedTabIndex = selectedDestination,
|
||||
) {
|
||||
destinations.forEachIndexed { index, destination ->
|
||||
Tab(
|
||||
selected = selectedDestination == index,
|
||||
onClick = {
|
||||
|
||||
selectedDestination = index
|
||||
},
|
||||
text = {
|
||||
Text(
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package ac.aux.compose.ui.view.model
|
||||
|
||||
import ac.aux.compose.database.model.SynchronizeNostrEventRequest
|
||||
import ac.aux.compose.database.model.types.SynchronizationFilter
|
||||
import ac.aux.compose.nostr.Relays
|
||||
import ac.aux.compose.repository.NostrRepository
|
||||
import ac.aux.compose.repository.SearchRepository
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.initializer
|
||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
import co.touchlab.kermit.Logger
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
||||
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.IO
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
enum class SearchResultType {
|
||||
Posts, Articles, Profiles, Photos, Videos
|
||||
}
|
||||
|
||||
class SearchResultViewModel(
|
||||
initialSearchResultType: SearchResultType,
|
||||
val nostrRepository: NostrRepository,
|
||||
val searchRepository: SearchRepository,
|
||||
): ViewModel() {
|
||||
val logger = Logger.withTag(TAG)
|
||||
|
||||
var searchResultType: SearchResultType by mutableStateOf(initialSearchResultType)
|
||||
private set
|
||||
|
||||
|
||||
fun updateSearchResultType (searchResultType: SearchResultType) {
|
||||
this.searchResultType = searchResultType
|
||||
|
||||
// TODO: Reload search results...
|
||||
}
|
||||
|
||||
fun performSearch(searchQuery: String) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
// Sync Notifications... might want to also run this in the background
|
||||
searchRepository.submitSearch(
|
||||
Relays.eventPublishRelaySet.map { normalizedRelay ->
|
||||
SynchronizeNostrEventRequest(
|
||||
purpose = "search",
|
||||
synchronizationFilters = arrayOf(
|
||||
SynchronizationFilter(
|
||||
kinds = arrayOf(
|
||||
TextNoteEvent.KIND,
|
||||
RepostEvent.KIND,
|
||||
ReactionEvent.KIND,
|
||||
LnZapEvent.KIND,
|
||||
),
|
||||
search = "shoes",
|
||||
limit = 50
|
||||
)
|
||||
),
|
||||
relayURL = normalizedRelay.url
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
const val TAG = "SearchViewModel"
|
||||
|
||||
fun factory(
|
||||
initialSearchResultType: SearchResultType = SearchResultType.Posts,
|
||||
nostrRepository: NostrRepository,
|
||||
searchRepository: SearchRepository,
|
||||
): ViewModelProvider.Factory = viewModelFactory {
|
||||
initializer {
|
||||
SearchResultViewModel(
|
||||
initialSearchResultType = initialSearchResultType,
|
||||
nostrRepository = nostrRepository,
|
||||
searchRepository = searchRepository
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user