Skip to content

Commit

Permalink
Add support for searching threads and nodes
Browse files Browse the repository at this point in the history
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
  • Loading branch information
theimpulson committed Mar 26, 2024
1 parent 8444d54 commit 62d008f
Show file tree
Hide file tree
Showing 12 changed files with 255 additions and 42 deletions.
25 changes: 25 additions & 0 deletions app/src/main/java/io/aayush/relabs/network/XDAInterface.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import io.aayush.relabs.network.data.node.Nodes
import io.aayush.relabs.network.data.post.PostInfo
import io.aayush.relabs.network.data.post.PostReply
import io.aayush.relabs.network.data.react.PostReact
import io.aayush.relabs.network.data.search.Order
import io.aayush.relabs.network.data.search.PostSearch
import io.aayush.relabs.network.data.search.SearchResultNode
import io.aayush.relabs.network.data.search.SearchResultThread
import io.aayush.relabs.network.data.thread.ThreadInfo
import io.aayush.relabs.network.data.thread.Threads
import io.aayush.relabs.network.data.user.Me
Expand Down Expand Up @@ -105,4 +109,25 @@ interface XDAInterface {

@POST("audapp-push-subscriptions")
suspend fun postExpoPushToken(@Body body: RequestBody): Response<Success>

@POST("audapp-search")
suspend fun postSearch(
@Query("keywords") query: String,
@Query("search_type") type: String,
@Query("c[container_only]") searchThreadConstraint: Int? = null,
@Query("c[title_only]") searchTitleConstraint: Int? = null,
@Query("order") order: String = Order.RELEVANCE.value,
): Response<PostSearch>

@GET("audapp-search/{id}")
suspend fun getSearchResultsForThread(
@Path("id") searchID: Int,
@Query("page") page: Int? = null,
): Response<SearchResultThread>

@GET("audapp-search/{id}")
suspend fun getSearchResultsForNode(
@Path("id") searchID: Int,
@Query("page") page: Int? = null,
): Response<SearchResultNode>
}
30 changes: 30 additions & 0 deletions app/src/main/java/io/aayush/relabs/network/XDARepository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ import io.aayush.relabs.network.data.post.PostInfo
import io.aayush.relabs.network.data.post.PostReply
import io.aayush.relabs.network.data.react.PostReact
import io.aayush.relabs.network.data.react.React
import io.aayush.relabs.network.data.search.Type
import io.aayush.relabs.network.data.thread.Thread
import io.aayush.relabs.network.data.thread.ThreadInfo
import io.aayush.relabs.network.data.thread.Threads
import io.aayush.relabs.network.data.user.Me
import io.aayush.relabs.network.paging.GenericPagingSource.Companion.createPager
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import java.util.UUID
import okhttp3.MultipartBody
import retrofit2.Response
Expand Down Expand Up @@ -136,6 +138,34 @@ class XDARepository @Inject constructor(
return safeExecute { xdaInterface.getWatchedNodes() }?.nodes
}

fun getSearchResultsForThreads(query: String): Flow<PagingData<Thread>> {
return createPager { page ->
val search = safeExecute { xdaInterface.postSearch(query, Type.THREAD.value) }?.search

if (search != null && search.id != 0) {
safeExecute {
xdaInterface.getSearchResultsForThread(search.id, page)
}?.results.orEmpty()
} else {
emptyList()
}
}.flow
}

fun getSearchResultsForNodes(query: String): Flow<PagingData<Node>> {
return createPager { page ->
val search = safeExecute { xdaInterface.postSearch(query, Type.NODE.value) }?.search

if (search != null && search.id != 0) {
safeExecute {
xdaInterface.getSearchResultsForNode(search.id, page)
}?.results.orEmpty()
} else {
emptyList()
}
}.flow
}

private inline fun <T> safeExecute(block: () -> Response<T>): T? {
return try {
val response = block()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.aayush.relabs.network.data.search

enum class Order(val value: String) {
RELEVANCE("relevance"),
DATE("date"),
MOST_RECENT("last_update"),
MOST_REPLIES("replies")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.aayush.relabs.network.data.search

data class PostSearch(
val search: Search = Search()
)
12 changes: 12 additions & 0 deletions app/src/main/java/io/aayush/relabs/network/data/search/Search.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.aayush.relabs.network.data.search

import io.aayush.relabs.network.data.common.DateTime

data class Search(
val created_at: DateTime = DateTime(),
val id: Int = 0,
val result_count: Int = 0,
val search_constraints: List<String> = emptyList(),
val search_order: String = String(),
val search_type: String = String()
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package io.aayush.relabs.network.data.search

data class SearchConstraints(
val container_only: Int = 0,
val title_only: Int = 0
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.aayush.relabs.network.data.search

import io.aayush.relabs.network.data.common.Pagination
import io.aayush.relabs.network.data.node.Node

data class SearchResultNode(
val search: Search = Search(),
val results: List<Node> = emptyList(),
val pagination: Pagination = Pagination()
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.aayush.relabs.network.data.search

import io.aayush.relabs.network.data.common.Pagination
import io.aayush.relabs.network.data.thread.Thread

data class SearchResultThread(
val search: Search = Search(),
val results: List<Thread> = emptyList(),
val pagination: Pagination = Pagination()
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package io.aayush.relabs.network.data.search

enum class Type(val value: String) {
NODE("node"),
THREAD("post")
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,47 +6,112 @@
package io.aayush.relabs.ui.screens.threadpreview

import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
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.lazy.LazyColumn
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SearchBar
import androidx.compose.material3.Tab
import androidx.compose.material3.TabRow
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEachIndexed
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavHostController
import androidx.paging.LoadState
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.itemContentType
import androidx.paging.compose.itemKey
import io.aayush.relabs.R
import io.aayush.relabs.network.data.thread.State
import io.aayush.relabs.network.data.thread.Thread
import io.aayush.relabs.ui.components.MainTopAppBar
import io.aayush.relabs.ui.components.ThreadPreviewItem
import io.aayush.relabs.ui.navigation.Screen
import kotlinx.coroutines.launch

@Composable
@OptIn(ExperimentalFoundationApi::class)
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
fun ThreadPreviewScreen(
navHostController: NavHostController,
viewModel: ThreadPreviewViewModel = hiltViewModel()
) {

val searchResults = viewModel.searchResults.collectAsLazyPagingItems()
val shouldShowSearchResults by viewModel.shouldShowSearchResults.collectAsStateWithLifecycle()
val isSearching by viewModel.isSearching.collectAsStateWithLifecycle()
val searchText by viewModel.searchQuery.collectAsStateWithLifecycle()

Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
MainTopAppBar(screen = Screen.ThreadPreview, navHostController = navHostController)
Box(modifier = Modifier.fillMaxWidth()) {
SearchBar(
modifier = Modifier.align(Alignment.Center),
query = searchText,
onQueryChange = viewModel::updateQuery,
onSearch = { viewModel.search(searchText) },
active = isSearching,
onActiveChange = {
viewModel.isSearching.value = it
if (!it) viewModel.updateQuery("")
},
placeholder = {
Text(text = stringResource(id = R.string.search_hint_threads))
},
leadingIcon = {
if (isSearching) {
IconButton(
onClick = {
viewModel.isSearching.value = false
viewModel.updateQuery("")
}
) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = ""
)
}
} else {
Icon(imageVector = Icons.Default.Search, contentDescription = "")
}
},
trailingIcon = {
if (isSearching) {
IconButton(
onClick = { viewModel.updateQuery("") },
enabled = searchText.isNotBlank()
) {
Icon(imageVector = Icons.Default.Close, contentDescription = "")
}
}
}
) {
if (shouldShowSearchResults) {
ThreadItems(currentThreads = searchResults, navHostController = navHostController)
}
}
}
}
) {
val tabData = listOf(R.string.watched, R.string.whats_new)
Expand Down Expand Up @@ -78,48 +143,53 @@ fun ThreadPreviewScreen(
}
}
HorizontalPager(state = pagerState) {
LazyColumn(modifier = Modifier.fillMaxHeight()) {
val currentThreads = when (it) {
0 -> watchedThreads
else -> trendingThreads
}
when (currentThreads.loadState.refresh) {
is LoadState.Error -> {}
is LoadState.Loading -> {
items(20) {
ThreadPreviewItem(
modifier = Modifier.padding(10.dp),
loading = true
)
}
}
val currentThreads = when (it) {
0 -> watchedThreads
else -> trendingThreads
}
ThreadItems(currentThreads = currentThreads, navHostController = navHostController)
}
}
}
}

@Composable
private fun ThreadItems(currentThreads: LazyPagingItems<Thread>, navHostController: NavHostController) {
LazyColumn(modifier = Modifier.fillMaxHeight()) {
when (currentThreads.loadState.refresh) {
is LoadState.Error -> {}
is LoadState.Loading -> {
items(20) {
ThreadPreviewItem(
modifier = Modifier.padding(10.dp),
loading = true
)
}
}

else -> {
items(
count = currentThreads.itemCount,
key = currentThreads.itemKey { t -> t.id },
contentType = currentThreads.itemContentType { Thread::class.java },
) { index ->
val thread = currentThreads[index] ?: return@items
if (thread.state != State.VISIBLE) return@items
else -> {
items(
count = currentThreads.itemCount,
key = currentThreads.itemKey { t -> t.id },
contentType = currentThreads.itemContentType { Thread::class.java },
) { index ->
val thread = currentThreads[index] ?: return@items
if (thread.state != State.VISIBLE) return@items

ThreadPreviewItem(
modifier = Modifier.padding(10.dp),
avatarURL = thread.user?.avatar?.data?.medium ?: String(),
title = thread.title,
author = thread.user?.username,
totalReplies = thread.reply_count,
views = thread.view_count,
lastReplyDate = thread.last_post_at.long,
forum = thread.node.title,
unread = thread.isUnread,
onClicked = {
navHostController.navigate(Screen.Thread.withID(thread.id))
}
)
}
ThreadPreviewItem(
modifier = Modifier.padding(10.dp),
avatarURL = thread.user?.avatar?.data?.medium ?: String(),
title = thread.title,
author = thread.user?.username,
totalReplies = thread.reply_count,
views = thread.view_count,
lastReplyDate = thread.last_post_at.long,
forum = thread.node.title,
unread = thread.isUnread,
onClicked = {
navHostController.navigate(Screen.Thread.withID(thread.id))
}
}
)
}
}
}
Expand Down

0 comments on commit 62d008f

Please sign in to comment.