Skip to content

Commit

Permalink
Merge pull request #5 from MuhammedEdrees/search_frag
Browse files Browse the repository at this point in the history
implemented search behaviour
  • Loading branch information
MuhammedEdrees authored Sep 10, 2023
2 parents 751f987 + 6643a00 commit d7d7e58
Show file tree
Hide file tree
Showing 14 changed files with 248 additions and 32 deletions.
4 changes: 3 additions & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ dependencies {
implementation 'com.github.bumptech.glide:glide:4.16.0'
implementation "com.squareup.moshi:moshi-kotlin:1.12.0"
implementation "com.squareup.retrofit2:converter-moshi:2.9.0"
// implementation 'jp.wasabeef:blurry:4.0.1'
implementation 'jp.wasabeef:glide-transformations:4.3.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1'
implementation "com.facebook.shimmer:shimmer:0.5.0"
}
8 changes: 8 additions & 0 deletions app/src/main/java/com/edrees/newsapp/network/APIClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.edrees.newsapp.network

import android.util.Log
import com.edrees.newsapp.model.ArticleResponse
import retrofit2.create

object APIClient: ArticleRemoteDataSource {
override suspend fun getTopHeadlinesByCountry(
Expand All @@ -13,4 +14,11 @@ object APIClient: ArticleRemoteDataSource {
category: String,
apiKey: String
) = BaseRetrofitHelper.retrofit.create(ArticleService::class.java).getTopHeadlinesByCategory(category, apiKey)

override suspend fun getQuerySearchResult(
query: String,
apiKey: String,
lang: String,
page: Int
) = BaseRetrofitHelper.retrofit.create(ArticleService::class.java).getQuerySearchResult(query, apiKey, lang, page)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package com.edrees.newsapp.network

import com.edrees.newsapp.model.ArticleResponse
import com.edrees.newsapp.model.Category
import retrofit2.http.Query

interface ArticleRemoteDataSource {
suspend fun getTopHeadlinesByCountry(country: String, apiKey: String): ArticleResponse
suspend fun getTopHeadlinesByCategory(category: String, apiKey: String): ArticleResponse
suspend fun getQuerySearchResult(query: String, apiKey: String, lang: String, page: Int): ArticleResponse
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ interface ArticleService {

@GET("top-headlines")
suspend fun getTopHeadlinesByCategory(@Query("category") category: String, @Query("apiKey") apiKey: String): ArticleResponse
@GET("everything")
suspend fun getQuerySearchResult(@Query("q") query: String, @Query("apiKey") apiKey: String, @Query("language") lang: String, @Query("page") page: Int): ArticleResponse
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ import com.edrees.newsapp.model.ArticleResponse
interface ArticleRepository {
suspend fun getTopHeadlinesByCountry( country: String, apiKey: String): ArticleResponse
suspend fun getTopHeadlinesByCategory(category: String, apiKey: String): ArticleResponse
suspend fun getQuerySearchResult(query: String, apiKey: String, lang: String, page: Int): ArticleResponse
suspend fun insertArticle(vararg articles: Article)
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ class ArticleRepositoryImpl(
return remoteSource.getTopHeadlinesByCategory(category, apiKey)
}

override suspend fun getQuerySearchResult(
query: String,
apiKey: String,
lang: String,
page: Int
): ArticleResponse {
return remoteSource.getQuerySearchResult(query, apiKey, lang, page)
}

override suspend fun insertArticle(vararg articles: Article) {
localSource.insertArticle(*articles)
}
Expand Down
5 changes: 4 additions & 1 deletion app/src/main/java/com/edrees/newsapp/ui/ViewModelFactory.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,18 @@ import androidx.lifecycle.ViewModelProvider
import com.edrees.newsapp.repo.ArticleRepository
import com.edrees.newsapp.ui.categories.categorized_news.CategorizedNewsViewModel
import com.edrees.newsapp.ui.home.HomeViewModel
import com.edrees.newsapp.ui.search.SearchViewModel

class ViewModelFactory(val repo: ArticleRepository): ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return if(modelClass.isAssignableFrom(HomeViewModel::class.java)) {
HomeViewModel(repo) as T
} else if(modelClass.isAssignableFrom(CategorizedNewsViewModel::class.java)) {
CategorizedNewsViewModel(repo) as T
} else if(modelClass.isAssignableFrom(SearchViewModel::class.java)){
SearchViewModel(repo) as T
} else {
throw IllegalArgumentException("No Matching Viewmodels!")
throw IllegalArgumentException("No Matching Viewmodels!")
}
}
}
50 changes: 50 additions & 0 deletions app/src/main/java/com/edrees/newsapp/ui/search/SearchAdapter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.edrees.newsapp.ui.search

import android.view.LayoutInflater
import android.view.View
import android.view.View.OnClickListener
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import com.edrees.newsapp.databinding.SearchListItemBinding
import com.edrees.newsapp.model.Article
import com.edrees.newsapp.ui.home.DetailsCallback
import jp.wasabeef.glide.transformations.BlurTransformation

class SearchAdapter(private val callback: DetailsCallback): RecyclerView.Adapter<SearchAdapter.ViewHolder>() {
private val data = mutableListOf<Article>()
inner class ViewHolder(val binding: SearchListItemBinding): RecyclerView.ViewHolder(binding.root) {
init{
binding.root.setOnClickListener {
this@SearchAdapter.callback.navigateToDetails(data[layoutPosition])
}
}
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = SearchListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding)
}

override fun getItemCount() = data.size

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
with(holder){
with(data[position]){
binding.titleTextView.text = title
binding.descriptionTextView.text = description
binding.sourceTextView.text = source?.name?:"Unknown Source"
Glide.with(binding.root)
.load(urlToImage)
.apply(RequestOptions.bitmapTransform(BlurTransformation(5, 1)))
.into(binding.itemThumbnail)
}
}
}
fun setData(newData: List<Article>){
data.clear()
data.addAll(newData)
notifyDataSetChanged()
}
}
74 changes: 62 additions & 12 deletions app/src/main/java/com/edrees/newsapp/ui/search/SearchFragment.kt
Original file line number Diff line number Diff line change
@@ -1,42 +1,92 @@
package com.edrees.newsapp.ui.search

import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.get
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.edrees.newsapp.databinding.FragmentSearchBinding
import com.edrees.newsapp.local.LocalSourceImpl
import com.edrees.newsapp.model.Article
import com.edrees.newsapp.network.APIClient
import com.edrees.newsapp.repo.ArticleRepositoryImpl
import com.edrees.newsapp.ui.ViewModelFactory
import com.edrees.newsapp.ui.home.DetailsCallback
import com.google.android.material.textfield.TextInputEditText

class SearchFragment : Fragment() {
class SearchFragment : Fragment(), DetailsCallback {

private var _binding: FragmentSearchBinding? = null

// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
private lateinit var viewModel: SearchViewModel
private lateinit var recyclerView: RecyclerView
private lateinit var layoutManager: LinearLayoutManager
private lateinit var searchEditText: TextInputEditText
private lateinit var adapter: SearchAdapter

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val slideshowViewModel =
ViewModelProvider(this).get(SearchViewModel::class.java)

_binding = FragmentSearchBinding.inflate(inflater, container, false)
val root: View = binding.root
return binding.root
}

val textView: TextView = binding.textSlideshow
slideshowViewModel.text.observe(viewLifecycleOwner) {
textView.text = it
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
prepareViewModel()
searchEditText = binding.searchTextInputLayout.editText as TextInputEditText
viewModel.listOfArticles.observe(viewLifecycleOwner){articles ->
if(searchEditText.text.isNullOrBlank()){
adapter.setData(listOf())
} else {
adapter.setData(articles)
}
}
return root
recyclerView = binding.searchRecyclerView
layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
adapter = SearchAdapter(this)
recyclerView.layoutManager = layoutManager
recyclerView.adapter = adapter
searchEditText.addTextChangedListener(object: TextWatcher{
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}

override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
if(p0.toString().isBlank()){
adapter.setData(listOf<Article>())
} else {
viewModel.search(p0.toString(), "en", 1)
}
}

override fun afterTextChanged(p0: Editable?) {
}
})
}

private fun prepareViewModel() {
val factory = ViewModelFactory(ArticleRepositoryImpl(APIClient, LocalSourceImpl(requireContext())))
viewModel = ViewModelProvider(this, factory).get(SearchViewModel::class.java)
}

override fun onDestroyView() {
adapter.setData(listOf())
super.onDestroyView()
_binding = null
}

override fun navigateToDetails(article: Article) {
val action = SearchFragmentDirections.actionNavSearchToDetailsFragment(article)
findNavController().navigate(action)
}
}
17 changes: 12 additions & 5 deletions app/src/main/java/com/edrees/newsapp/ui/search/SearchViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,18 @@ package com.edrees.newsapp.ui.search
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.edrees.newsapp.model.Article
import com.edrees.newsapp.repo.ArticleRepository
import kotlinx.coroutines.launch

class SearchViewModel : ViewModel() {

private val _text = MutableLiveData<String>().apply {
value = "This is slideshow Fragment"
class SearchViewModel(private val repo: ArticleRepository) : ViewModel() {
private val apiKey = "cfb9fb4a523748199c5f64423f1ef4ed"
private val _listOfArticle = MutableLiveData<List<Article>>()
val listOfArticles: LiveData<List<Article>> = _listOfArticle
fun search(query: String, lang: String, page: Int){
viewModelScope.launch{
_listOfArticle.value = repo.getQuerySearchResult(query, apiKey, lang, page).articles
}
}
val text: LiveData<String> = _text
}
11 changes: 11 additions & 0 deletions app/src/main/res/drawable/image_shade.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:angle="90"
android:endColor="#88000000"
android:startColor="#88000000"
android:centerColor="#88000000" />

<corners android:radius="0dp" />
</shape>
27 changes: 16 additions & 11 deletions app/src/main/res/layout/fragment_search.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,22 @@
android:layout_height="match_parent"
tools:context=".ui.search.SearchFragment">

<TextView
android:id="@+id/text_slideshow"
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:textAlignment="center"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
android:layout_margin="8dp"
app:layout_constraintTop_toTopOf="parent"
android:id="@+id/search_text_input_layout"
app:endIconMode="clear_text"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
app:startIconDrawable="@drawable/ic_menu_search">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.google.android.material.textfield.TextInputLayout>
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/search_recycler_view"
app:layout_constraintTop_toBottomOf="@+id/search_text_input_layout"/>
</androidx.constraintlayout.widget.ConstraintLayout>
58 changes: 58 additions & 0 deletions app/src/main/res/layout/search_list_item.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:cardCornerRadius="12dp"
android:elevation="12dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="@id/text_views_layout"
app:layout_constraintBottom_toBottomOf="@id/text_views_layout"
android:scaleType="centerCrop"
android:id="@+id/item_thumbnail"
android:foreground="@drawable/image_shade"
android:src="@drawable/ic_splash"/>
<LinearLayout
android:id="@+id/text_views_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/titleTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="Title Placeholder"
android:textAppearance="@style/HeadlineText" />
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:background="#aaeeeeee"/>
<TextView
android:id="@+id/descriptionTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textColor="@color/white"
android:textSize="14sp" />
<TextView
android:id="@+id/sourceTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="end"
android:layout_gravity="bottom"
tools:text="Source"
android:textAppearance="@style/SubText"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
Loading

0 comments on commit d7d7e58

Please sign in to comment.