From 6643a00c78f3573c59153e99cca7563f4e29438f Mon Sep 17 00:00:00 2001 From: Edrees Date: Sat, 9 Sep 2023 23:48:54 +0300 Subject: [PATCH] implemented search behaviour --- app/build.gradle | 4 +- .../com/edrees/newsapp/network/APIClient.kt | 8 ++ .../network/ArticleRemoteDataSource.kt | 2 + .../edrees/newsapp/network/ArticleService.kt | 2 + .../edrees/newsapp/repo/ArticleRepository.kt | 1 + .../newsapp/repo/ArticleRepositoryImpl.kt | 9 +++ .../com/edrees/newsapp/ui/ViewModelFactory.kt | 5 +- .../edrees/newsapp/ui/search/SearchAdapter.kt | 50 +++++++++++++ .../newsapp/ui/search/SearchFragment.kt | 74 ++++++++++++++++--- .../newsapp/ui/search/SearchViewModel.kt | 17 +++-- app/src/main/res/drawable/image_shade.xml | 11 +++ app/src/main/res/layout/fragment_search.xml | 27 ++++--- app/src/main/res/layout/search_list_item.xml | 58 +++++++++++++++ .../main/res/navigation/mobile_navigation.xml | 12 ++- 14 files changed, 248 insertions(+), 32 deletions(-) create mode 100644 app/src/main/java/com/edrees/newsapp/ui/search/SearchAdapter.kt create mode 100644 app/src/main/res/drawable/image_shade.xml create mode 100644 app/src/main/res/layout/search_list_item.xml diff --git a/app/build.gradle b/app/build.gradle index 7c22770..7746b8b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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" } \ No newline at end of file diff --git a/app/src/main/java/com/edrees/newsapp/network/APIClient.kt b/app/src/main/java/com/edrees/newsapp/network/APIClient.kt index 787234d..b9340b2 100644 --- a/app/src/main/java/com/edrees/newsapp/network/APIClient.kt +++ b/app/src/main/java/com/edrees/newsapp/network/APIClient.kt @@ -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( @@ -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) } \ No newline at end of file diff --git a/app/src/main/java/com/edrees/newsapp/network/ArticleRemoteDataSource.kt b/app/src/main/java/com/edrees/newsapp/network/ArticleRemoteDataSource.kt index 6aa0b41..9126335 100644 --- a/app/src/main/java/com/edrees/newsapp/network/ArticleRemoteDataSource.kt +++ b/app/src/main/java/com/edrees/newsapp/network/ArticleRemoteDataSource.kt @@ -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 } \ No newline at end of file diff --git a/app/src/main/java/com/edrees/newsapp/network/ArticleService.kt b/app/src/main/java/com/edrees/newsapp/network/ArticleService.kt index 7779ba3..53bfce2 100644 --- a/app/src/main/java/com/edrees/newsapp/network/ArticleService.kt +++ b/app/src/main/java/com/edrees/newsapp/network/ArticleService.kt @@ -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 } \ No newline at end of file diff --git a/app/src/main/java/com/edrees/newsapp/repo/ArticleRepository.kt b/app/src/main/java/com/edrees/newsapp/repo/ArticleRepository.kt index 4a3833e..5d7d27a 100644 --- a/app/src/main/java/com/edrees/newsapp/repo/ArticleRepository.kt +++ b/app/src/main/java/com/edrees/newsapp/repo/ArticleRepository.kt @@ -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) } \ No newline at end of file diff --git a/app/src/main/java/com/edrees/newsapp/repo/ArticleRepositoryImpl.kt b/app/src/main/java/com/edrees/newsapp/repo/ArticleRepositoryImpl.kt index e01d5f2..99c7941 100644 --- a/app/src/main/java/com/edrees/newsapp/repo/ArticleRepositoryImpl.kt +++ b/app/src/main/java/com/edrees/newsapp/repo/ArticleRepositoryImpl.kt @@ -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) } diff --git a/app/src/main/java/com/edrees/newsapp/ui/ViewModelFactory.kt b/app/src/main/java/com/edrees/newsapp/ui/ViewModelFactory.kt index 69797b6..6d63c8b 100644 --- a/app/src/main/java/com/edrees/newsapp/ui/ViewModelFactory.kt +++ b/app/src/main/java/com/edrees/newsapp/ui/ViewModelFactory.kt @@ -5,6 +5,7 @@ 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 create(modelClass: Class): T { @@ -12,8 +13,10 @@ class ViewModelFactory(val repo: ArticleRepository): ViewModelProvider.Factory { 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!") } } } \ No newline at end of file diff --git a/app/src/main/java/com/edrees/newsapp/ui/search/SearchAdapter.kt b/app/src/main/java/com/edrees/newsapp/ui/search/SearchAdapter.kt new file mode 100644 index 0000000..a5ee1d4 --- /dev/null +++ b/app/src/main/java/com/edrees/newsapp/ui/search/SearchAdapter.kt @@ -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() { + private val data = mutableListOf
() + 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
){ + data.clear() + data.addAll(newData) + notifyDataSetChanged() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/edrees/newsapp/ui/search/SearchFragment.kt b/app/src/main/java/com/edrees/newsapp/ui/search/SearchFragment.kt index 93019c3..7a8fc38 100644 --- a/app/src/main/java/com/edrees/newsapp/ui/search/SearchFragment.kt +++ b/app/src/main/java/com/edrees/newsapp/ui/search/SearchFragment.kt @@ -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
()) + } 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) + } } \ No newline at end of file diff --git a/app/src/main/java/com/edrees/newsapp/ui/search/SearchViewModel.kt b/app/src/main/java/com/edrees/newsapp/ui/search/SearchViewModel.kt index 1954fa6..cdfeaff 100644 --- a/app/src/main/java/com/edrees/newsapp/ui/search/SearchViewModel.kt +++ b/app/src/main/java/com/edrees/newsapp/ui/search/SearchViewModel.kt @@ -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().apply { - value = "This is slideshow Fragment" +class SearchViewModel(private val repo: ArticleRepository) : ViewModel() { + private val apiKey = "cfb9fb4a523748199c5f64423f1ef4ed" + private val _listOfArticle = MutableLiveData>() + val listOfArticles: LiveData> = _listOfArticle + fun search(query: String, lang: String, page: Int){ + viewModelScope.launch{ + _listOfArticle.value = repo.getQuerySearchResult(query, apiKey, lang, page).articles + } } - val text: LiveData = _text } \ No newline at end of file diff --git a/app/src/main/res/drawable/image_shade.xml b/app/src/main/res/drawable/image_shade.xml new file mode 100644 index 0000000..8612fbf --- /dev/null +++ b/app/src/main/res/drawable/image_shade.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml index afb3631..a02868e 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_search.xml @@ -6,17 +6,22 @@ android:layout_height="match_parent" tools:context=".ui.search.SearchFragment"> - + 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"> + + + \ No newline at end of file diff --git a/app/src/main/res/layout/search_list_item.xml b/app/src/main/res/layout/search_list_item.xml new file mode 100644 index 0000000..dd76338 --- /dev/null +++ b/app/src/main/res/layout/search_list_item.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/mobile_navigation.xml b/app/src/main/res/navigation/mobile_navigation.xml index 576aef9..7304c5e 100644 --- a/app/src/main/res/navigation/mobile_navigation.xml +++ b/app/src/main/res/navigation/mobile_navigation.xml @@ -29,12 +29,20 @@ android:id="@+id/nav_search" android:name="com.edrees.newsapp.ui.search.SearchFragment" android:label="@string/menu_search" - tools:layout="@layout/fragment_search" /> + tools:layout="@layout/fragment_search" > + + + tools:layout="@layout/fragment_favorites" > + +