Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions core/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
android:name=".ui.WebViewWithSSOActivity"
android:configChanges="orientation|keyboardHidden|screenSize" />

<activity
android:name=".ui.GlobalSearchActivity"
android:configChanges="orientation|keyboardHidden|screenSize" />

<receiver android:name=".util.FileDownloaderBroadcastReceiver"
android:exported="true">
<intent-filter>
Expand Down
18 changes: 18 additions & 0 deletions core/src/main/java/in/testpress/models/Search.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package `in`.testpress.models

data class SearchApiResponse(
val results: List<SearchResult>,
val nextPage: Int?
)

data class SearchResult(
val title: String?,
val highlight: Highlight?,
val active: Boolean?,
val type: String?,
val id: Int?
)

data class Highlight(
val title: String?
)
18 changes: 14 additions & 4 deletions core/src/main/java/in/testpress/network/TestpressAPIService.kt
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
package `in`.testpress.network

import `in`.testpress.core.TestpressSdk
import `in`.testpress.models.NetworkCategory
import `in`.testpress.models.NetworkDiscussionThreadAnswer
import `in`.testpress.models.NetworkForum
import `in`.testpress.models.TestpressApiResponse
import android.content.Context
import `in`.testpress.models.*
import retrofit2.Response
import retrofit2.http.GET
import retrofit2.http.Path
import retrofit2.http.Query
import retrofit2.http.QueryMap


const val URL_FORUMS_FRAG = "api/v2.5/discussions/"
const val FORUM_CATEGORIES_URL = "api/v2.3/posts/categories/"
const val GLOBAL_SEARCH_PATH = "/api/v2.5/global_search/search_results/"

@JvmSuppressWildcards
interface TestpressAPIService {
Expand All @@ -31,6 +30,13 @@ interface TestpressAPIService {
fun getDiscussionAnswer(
@Path(value = "discussion_id", encoded = true) discussionId: Long
): RetrofitCall<NetworkDiscussionThreadAnswer>

@GET(GLOBAL_SEARCH_PATH)
suspend fun getGlobalSearch(
@QueryMap queryParams: Map<String, Any>,
@Query("param") filterParams: List<String>,
@Query("chaptercontent_content_type") filterContentTypes: List<String>
): SearchApiResponse
}

open class APIClient(context: Context): TestpressApiClient(context, TestpressSdk.getTestpressSession(context)) {
Expand All @@ -48,4 +54,8 @@ open class APIClient(context: Context): TestpressApiClient(context, TestpressSdk
fun getDiscussionAnswer(discussionId: Long): RetrofitCall<NetworkDiscussionThreadAnswer> {
return getService().getDiscussionAnswer(discussionId)
}

suspend fun getGlobalSearch(queryParams: Map<String, Any>, filterQueryParams: Pair<List<String>,List<String>>): SearchApiResponse {
return getService().getGlobalSearch(queryParams, filterQueryParams.first,filterQueryParams.second)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package `in`.testpress.repository

import androidx.paging.PagingSource
import androidx.paging.PagingState
import `in`.testpress.models.SearchResult
import `in`.testpress.network.APIClient

class GlobalSearchPagingSource(
private val apiClient: APIClient,
private val queryParams: Map<String, Any>,
private val filterQueryParams: Pair<List<String>, List<String>>
) : PagingSource<Int, SearchResult>() {

override suspend fun load(params: LoadParams<Int>): LoadResult<Int, SearchResult> {
return try {
val mutableMap = queryParams.toMutableMap()
mutableMap["page"] = params.key ?: 1
mutableMap["size"] = 20
val response = apiClient.getGlobalSearch(mutableMap, filterQueryParams)

LoadResult.Page(
data = response.results,
prevKey = null,
nextKey = response.nextPage
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}

override fun getRefreshKey(state: PagingState<Int, SearchResult>): Int? {
return state.anchorPosition?.let { anchorPosition ->
val anchorPage = state.closestPageToPosition(anchorPosition)
anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package `in`.testpress.repository

import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import `in`.testpress.models.SearchResult
import `in`.testpress.network.APIClient
import kotlinx.coroutines.flow.Flow

class GlobalSearchRepository(private val apiClient: APIClient) {

fun getGlobalSearchResults(query: Map<String, Any>, filterQueryParams: Pair<List<String>, List<String>>): Flow<PagingData<SearchResult>> {
return Pager(
config = PagingConfig(pageSize = 15),
pagingSourceFactory = { GlobalSearchPagingSource(apiClient, query, filterQueryParams) }
).flow
}
}
18 changes: 18 additions & 0 deletions core/src/main/java/in/testpress/ui/GlobalSearchActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package `in`.testpress.ui

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import `in`.testpress.R
import `in`.testpress.ui.fragments.GlobalSearchFragment

class GlobalSearchActivity : AppCompatActivity() {

public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.global_search_activity_layout)
val fragment = GlobalSearchFragment()
supportFragmentManager.beginTransaction()
.replace(R.id.global_search_fragment_container, fragment).commitAllowingStateLoss()
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package `in`.testpress.ui.adapter

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.paging.LoadState
import androidx.paging.LoadStateAdapter
import androidx.recyclerview.widget.RecyclerView
import `in`.testpress.R
import `in`.testpress.databinding.TestpressBaseListFooterAdapterBinding

class BaseListFooterAdapter(private val retry: () -> Unit) :
LoadStateAdapter<BaseListFooterAdapter.BaseListFooterViewHolder>() {
override fun onBindViewHolder(holder: BaseListFooterViewHolder, loadState: LoadState) {
holder.bind(loadState)
}

override fun onCreateViewHolder(
parent: ViewGroup,
loadState: LoadState
): BaseListFooterViewHolder {
return BaseListFooterViewHolder.create(parent, retry)
}

class BaseListFooterViewHolder(
private val binding: TestpressBaseListFooterAdapterBinding,
retry: () -> Unit
) : RecyclerView.ViewHolder(binding.root) {

init {
binding.retryButton.setOnClickListener { retry.invoke() }
}

fun bind(loadState: LoadState) {
if (loadState is LoadState.Error) {
showErrorMessage(loadState)
}
binding.progressBar.isVisible = loadState is LoadState.Loading
binding.retryButton.isVisible = loadState is LoadState.Error
binding.errorMessageContainer.isVisible = loadState is LoadState.Error
}

private fun showErrorMessage(loadState: LoadState.Error) {
if (loadState.error.localizedMessage?.contains("404") == true) {
binding.emptyTitle.text = "Content Not Found"
binding.emptyDescription.text = "Content Not Found, Please try after some time"
}
}

companion object {
fun create(parent: ViewGroup, retry: () -> Unit): BaseListFooterViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.testpress_base_list_footer_adapter, parent, false)
val binding = TestpressBaseListFooterAdapterBinding.bind(view)
return BaseListFooterViewHolder(binding, retry)
}
}
}
}

61 changes: 61 additions & 0 deletions core/src/main/java/in/testpress/ui/adapter/GlobalSearchAdapter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package `in`.testpress.ui.adapter

import android.text.Html
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import `in`.testpress.databinding.SearchResultItemBinding
import `in`.testpress.models.SearchResult

class GlobalSearchAdapter :
PagingDataAdapter<SearchResult, GlobalSearchAdapter.SearchResultHolder>(ARTICLE_DIFF_CALLBACK) {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchResultHolder =
SearchResultHolder(
SearchResultItemBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false,
)
)

override fun onBindViewHolder(holder: SearchResultHolder, position: Int) {
val tile = getItem(position)
if (tile != null) {
holder.bind(tile)
}
}

class SearchResultHolder(
private val binding: SearchResultItemBinding
) : RecyclerView.ViewHolder(binding.root) {

fun bind(searchResult: SearchResult) {
binding.apply {
val title = convertHighlightToInlineStyle(searchResult.highlight?.title ?: "")
binding.title.text = Html.fromHtml(title)
binding.type.text = searchResult.type
binding.active.text = searchResult.active.toString()
}
}

private fun convertHighlightToInlineStyle(htmlResponse: String): String {
return htmlResponse.replace(
"class=\'highlight\'",
"style=\'background-color: yellow;\'"
)
}
}

companion object {
private val ARTICLE_DIFF_CALLBACK = object : DiffUtil.ItemCallback<SearchResult>() {
override fun areItemsTheSame(oldItem: SearchResult, newItem: SearchResult): Boolean =
oldItem.id == newItem.id

override fun areContentsTheSame(oldItem: SearchResult, newItem: SearchResult): Boolean =
oldItem == newItem
}
}
}
Loading