From eb374ec555894451b4440595077ed0dda5d73c5e Mon Sep 17 00:00:00 2001 From: Mina Mikhail Date: Fri, 20 Aug 2021 15:20:10 +0200 Subject: [PATCH] + Use DataBinding instead of ViewBinding in order to handle click listeners and different states of loading data inside recyclerView. + Handle loading state for article details webView. --- README.md | 3 +- app/build.gradle | 4 +- .../newsapp/core/enums/DataStatus.kt | 11 ++ .../newsapp/core/view/BaseActivity.kt | 13 +- .../newsapp/core/view/BaseFragment.kt | 32 +-- .../newsapp/core/view/BaseViewModel.kt | 10 + .../news/presentation/NewsActivity.kt | 2 +- .../features/news/presentation/NewsAdapter.kt | 13 +- .../breaking_news/BreakingNewsFragment.kt | 71 ++----- .../breaking_news/BreakingNewsViewModel.kt | 4 +- .../news_details/NewsDetailsFragment.kt | 48 ++++- .../news_details/NewsDetailsViewModel.kt | 13 +- .../saved_news/SavedNewsFragment.kt | 5 +- .../saved_news/SavedNewsViewModel.kt | 4 +- .../search_news/SearchNewsFragment.kt | 88 +++------ .../search_news/SearchNewsViewModel.kt | 11 +- .../splash/presentation/SplashActivity.kt | 3 +- app/src/main/res/layout/activity_news.xml | 41 ++-- app/src/main/res/layout/activity_splash.xml | 27 +-- .../res/layout/fragment_breaking_news.xml | 36 ++-- .../main/res/layout/fragment_news_details.xml | 61 ++++-- .../main/res/layout/fragment_saved_news.xml | 34 ++-- .../main/res/layout/fragment_search_news.xml | 187 ++++++++++-------- app/src/main/res/layout/item_news.xml | 127 ++++++------ app/src/main/res/layout/list_general.xml | 79 ++++---- app/src/main/res/layout/toast.xml | 63 ------ gradle.properties | 3 +- 27 files changed, 489 insertions(+), 504 deletions(-) create mode 100644 app/src/main/java/com/mina_mikhail/newsapp/core/enums/DataStatus.kt create mode 100644 app/src/main/java/com/mina_mikhail/newsapp/core/view/BaseViewModel.kt delete mode 100644 app/src/main/res/layout/toast.xml diff --git a/README.md b/README.md index 223efa1..a6c9dd2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ ## Tech stack & News App libraries - Navigation component - navigation graph for navigating and replacing screens/fragments -- ViewBinding - allows to more easily write code that interacts with views and replaces ```findViewById```. +- DataBinding - allows to more easily write code that interacts with views and replaces ```findViewById```, handle + click listeners and different states of loading data inside recyclerView. - ViewModel - UI related data holder, lifecycle aware. - LiveData - Build data objects that notify views when the underlying database changes. - Dagger-Hilt for dependency injection. Object creation and scoping is handled by Hilt. diff --git a/app/build.gradle b/app/build.gradle index eeb8863..7457290 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -35,8 +35,8 @@ android { } } - buildFeatures { - viewBinding true + dataBinding { + enabled = true } } diff --git a/app/src/main/java/com/mina_mikhail/newsapp/core/enums/DataStatus.kt b/app/src/main/java/com/mina_mikhail/newsapp/core/enums/DataStatus.kt new file mode 100644 index 0000000..ff02600 --- /dev/null +++ b/app/src/main/java/com/mina_mikhail/newsapp/core/enums/DataStatus.kt @@ -0,0 +1,11 @@ +package com.mina_mikhail.newsapp.core.enums + +object DataStatus { + + const val LOADING = 1 + const val SHOW_DATA = 2 + const val NO_DATA = 3 + const val NO_INTERNET = 4 + const val LOADING_NEXT_PAGE = 5 + +} \ No newline at end of file diff --git a/app/src/main/java/com/mina_mikhail/newsapp/core/view/BaseActivity.kt b/app/src/main/java/com/mina_mikhail/newsapp/core/view/BaseActivity.kt index b138197..6ceedb4 100644 --- a/app/src/main/java/com/mina_mikhail/newsapp/core/view/BaseActivity.kt +++ b/app/src/main/java/com/mina_mikhail/newsapp/core/view/BaseActivity.kt @@ -1,12 +1,14 @@ package com.mina_mikhail.newsapp.core.view import android.os.Bundle +import androidx.annotation.LayoutRes import androidx.appcompat.app.AppCompatActivity +import androidx.databinding.DataBindingUtil +import androidx.databinding.ViewDataBinding import androidx.lifecycle.LiveData import androidx.navigation.NavController -import androidx.viewbinding.ViewBinding -abstract class BaseActivity : AppCompatActivity() { +abstract class BaseActivity : AppCompatActivity() { private var _binding: VB? = null open val binding get() = _binding!! @@ -33,10 +35,13 @@ abstract class BaseActivity : AppCompatActivity() { } private fun initViewBinding() { - _binding = getViewBinding() + _binding = DataBindingUtil.setContentView(this, getLayoutId()) + binding.lifecycleOwner = this + binding.executePendingBindings() } - abstract fun getViewBinding(): VB + @LayoutRes + abstract fun getLayoutId(): Int open fun setUpBottomNavigation() {} diff --git a/app/src/main/java/com/mina_mikhail/newsapp/core/view/BaseFragment.kt b/app/src/main/java/com/mina_mikhail/newsapp/core/view/BaseFragment.kt index 29f2b9a..c7c92f1 100644 --- a/app/src/main/java/com/mina_mikhail/newsapp/core/view/BaseFragment.kt +++ b/app/src/main/java/com/mina_mikhail/newsapp/core/view/BaseFragment.kt @@ -5,12 +5,14 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.databinding.DataBindingUtil +import androidx.databinding.ViewDataBinding import androidx.fragment.app.Fragment -import androidx.viewbinding.ViewBinding import com.mina_mikhail.newsapp.core.utils.hideLoadingDialog import com.mina_mikhail.newsapp.core.utils.showLoadingDialog -abstract class BaseFragment : Fragment() { +abstract class BaseFragment : Fragment() { private var _binding: VB? = null open val binding get() = _binding!! @@ -28,8 +30,10 @@ abstract class BaseFragment : Fragment() { } private fun initViewBinding(inflater: LayoutInflater, container: ViewGroup?) { - _binding = getViewBinding(inflater, container) + _binding = DataBindingUtil.inflate(inflater, getLayoutId(), container, false) mRootView = binding.root + binding.lifecycleOwner = this + binding.executePendingBindings() } override @@ -51,28 +55,37 @@ abstract class BaseFragment : Fragment() { super.onViewCreated(view, savedInstanceState) if (!hasInitializedRootView) { + getFragmentArguments() + setBindingVariables() setUpViews() observeAPICall() handleClickListeners() - subscribeToObservables(); + setupObservers() hasInitializedRootView = true } } - abstract fun getViewBinding(inflater: LayoutInflater, container: ViewGroup?): VB + @LayoutRes + abstract fun getLayoutId(): Int open fun registerListeners() {} open fun unRegisterListeners() {} + open fun getFragmentArguments() {} + + open fun setBindingVariables() {} + open fun setUpViews() {} open fun observeAPICall() {} open fun handleClickListeners() {} - open fun subscribeToObservables() {} + open fun setupObservers() { + + } fun showLoading() { hideLoading() @@ -85,11 +98,4 @@ abstract class BaseFragment : Fragment() { } fun hideLoading() = hideLoadingDialog(progressDialog, requireActivity()) - - override - fun onDestroyView() { - super.onDestroyView() - - _binding = null - } } \ No newline at end of file diff --git a/app/src/main/java/com/mina_mikhail/newsapp/core/view/BaseViewModel.kt b/app/src/main/java/com/mina_mikhail/newsapp/core/view/BaseViewModel.kt new file mode 100644 index 0000000..60bce3a --- /dev/null +++ b/app/src/main/java/com/mina_mikhail/newsapp/core/view/BaseViewModel.kt @@ -0,0 +1,10 @@ +package com.mina_mikhail.newsapp.core.view + +import androidx.lifecycle.ViewModel +import com.mina_mikhail.newsapp.core.utils.SingleLiveEvent + +open class BaseViewModel : ViewModel() { + + var dataLoadingEvent: SingleLiveEvent = SingleLiveEvent() + +} \ No newline at end of file diff --git a/app/src/main/java/com/mina_mikhail/newsapp/features/news/presentation/NewsActivity.kt b/app/src/main/java/com/mina_mikhail/newsapp/features/news/presentation/NewsActivity.kt index 95ea363..0ddbcb2 100644 --- a/app/src/main/java/com/mina_mikhail/newsapp/features/news/presentation/NewsActivity.kt +++ b/app/src/main/java/com/mina_mikhail/newsapp/features/news/presentation/NewsActivity.kt @@ -10,7 +10,7 @@ import dagger.hilt.android.AndroidEntryPoint class NewsActivity : BaseActivity() { override - fun getViewBinding() = ActivityNewsBinding.inflate(layoutInflater) + fun getLayoutId() = R.layout.activity_news override fun setUpBottomNavigation() { diff --git a/app/src/main/java/com/mina_mikhail/newsapp/features/news/presentation/NewsAdapter.kt b/app/src/main/java/com/mina_mikhail/newsapp/features/news/presentation/NewsAdapter.kt index 980b309..3b083eb 100644 --- a/app/src/main/java/com/mina_mikhail/newsapp/features/news/presentation/NewsAdapter.kt +++ b/app/src/main/java/com/mina_mikhail/newsapp/features/news/presentation/NewsAdapter.kt @@ -1,12 +1,12 @@ package com.mina_mikhail.newsapp.features.news.presentation import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup +import androidx.databinding.DataBindingUtil import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView -import com.mina_mikhail.newsapp.R +import com.mina_mikhail.newsapp.R.layout import com.mina_mikhail.newsapp.core.utils.DateUtils import com.mina_mikhail.newsapp.core.utils.convertDateTimeToTimesAgo import com.mina_mikhail.newsapp.core.view.extensions.loadRoundImage @@ -33,8 +33,10 @@ class NewsAdapter(private var itemClick: (Article) -> Unit) : ListAdapter( + LayoutInflater.from(parent.context), layout.item_news, parent, false + ) + return ArticlesViewHolder(binding) } override @@ -45,9 +47,8 @@ class NewsAdapter(private var itemClick: (Article) -> Unit) : ListAdapter() { private lateinit var scrollListener: EndlessRecyclerViewScrollListener override - fun getViewBinding(inflater: LayoutInflater, container: ViewGroup?) = - FragmentBreakingNewsBinding.inflate(inflater, container, false) + fun getLayoutId() = R.layout.fragment_breaking_news + + override + fun setBindingVariables() { + binding.includedList.baseViewModel = viewModel + } override fun setUpViews() { @@ -80,16 +81,14 @@ class BreakingNewsFragment : BaseFragment() { when (it) { is Loading -> { if (articlesAdapter.currentList.isNullOrEmpty()) { - showDataLoading() + viewModel.dataLoadingEvent.value = DataStatus.LOADING } else { - showPaginationLoading() + viewModel.dataLoadingEvent.value = DataStatus.LOADING_NEXT_PAGE } } is Empty -> { if (articlesAdapter.currentList.isNullOrEmpty()) { - showNoData() - } else { - hidePaginationLoading() + viewModel.dataLoadingEvent.value = DataStatus.NO_DATA } } is Success -> { @@ -106,15 +105,10 @@ class BreakingNewsFragment : BaseFragment() { articlesAdapter.submitList(articlesAdapter.currentList + it.value.articles) } - showData() + viewModel.dataLoadingEvent.value = DataStatus.SHOW_DATA } is Failure -> { - if (articlesAdapter.currentList.isNullOrEmpty()) { - handleApiError(it, noDataAction = { showNoData() }, noInternetAction = { showNoInternet() }) - } else { - handleApiError(it) - hidePaginationLoading() - } + handleApiError(it) } } }) @@ -150,45 +144,4 @@ class BreakingNewsFragment : BaseFragment() { bundle ) } - - private fun showDataLoading() { - binding.includedList.container.show() - binding.includedList.progressBar.show() - binding.includedList.emptyViewContainer.hide() - binding.includedList.internetErrorViewContainer.hide() - binding.includedList.recyclerView.hide() - binding.includedList.paginationProgressBar.hide() - } - - private fun showPaginationLoading() { - binding.includedList.paginationProgressBar.show() - } - - private fun hidePaginationLoading() { - binding.includedList.paginationProgressBar.hide() - } - - private fun showData() { - binding.includedList.recyclerView.show() - binding.includedList.container.hide() - binding.includedList.paginationProgressBar.hide() - } - - private fun showNoData() { - binding.includedList.container.show() - binding.includedList.emptyViewContainer.show() - binding.includedList.internetErrorViewContainer.hide() - binding.includedList.progressBar.hide() - binding.includedList.paginationProgressBar.hide() - binding.includedList.recyclerView.hide() - } - - private fun showNoInternet() { - binding.includedList.container.show() - binding.includedList.internetErrorViewContainer.show() - binding.includedList.emptyViewContainer.hide() - binding.includedList.progressBar.hide() - binding.includedList.paginationProgressBar.hide() - binding.includedList.recyclerView.hide() - } } \ No newline at end of file diff --git a/app/src/main/java/com/mina_mikhail/newsapp/features/news/presentation/breaking_news/BreakingNewsViewModel.kt b/app/src/main/java/com/mina_mikhail/newsapp/features/news/presentation/breaking_news/BreakingNewsViewModel.kt index 26e2007..4e45717 100644 --- a/app/src/main/java/com/mina_mikhail/newsapp/features/news/presentation/breaking_news/BreakingNewsViewModel.kt +++ b/app/src/main/java/com/mina_mikhail/newsapp/features/news/presentation/breaking_news/BreakingNewsViewModel.kt @@ -1,15 +1,15 @@ package com.mina_mikhail.newsapp.features.news.presentation.breaking_news -import androidx.lifecycle.ViewModel import androidx.lifecycle.liveData import com.mina_mikhail.newsapp.core.network.Resource +import com.mina_mikhail.newsapp.core.view.BaseViewModel import com.mina_mikhail.newsapp.features.news.data.repository.NewsRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import javax.inject.Inject @HiltViewModel -class BreakingNewsViewModel @Inject constructor(private val repository: NewsRepository) : ViewModel() { +class BreakingNewsViewModel @Inject constructor(private val repository: NewsRepository) : BaseViewModel() { var shouldLoadMore = false var isLoading = false diff --git a/app/src/main/java/com/mina_mikhail/newsapp/features/news/presentation/news_details/NewsDetailsFragment.kt b/app/src/main/java/com/mina_mikhail/newsapp/features/news/presentation/news_details/NewsDetailsFragment.kt index de4c461..3db5176 100644 --- a/app/src/main/java/com/mina_mikhail/newsapp/features/news/presentation/news_details/NewsDetailsFragment.kt +++ b/app/src/main/java/com/mina_mikhail/newsapp/features/news/presentation/news_details/NewsDetailsFragment.kt @@ -1,9 +1,12 @@ package com.mina_mikhail.newsapp.features.news.presentation.news_details import android.annotation.SuppressLint -import android.view.LayoutInflater -import android.view.ViewGroup +import android.os.Build.VERSION_CODES +import android.webkit.WebChromeClient +import android.webkit.WebResourceRequest +import android.webkit.WebView import android.webkit.WebViewClient +import androidx.annotation.RequiresApi import androidx.fragment.app.viewModels import androidx.navigation.fragment.navArgs import com.mina_mikhail.newsapp.R @@ -22,12 +25,22 @@ class NewsDetailsFragment : BaseFragment() { private lateinit var article: Article override - fun getViewBinding(inflater: LayoutInflater, container: ViewGroup?) = - FragmentNewsDetailsBinding.inflate(inflater, container, false) + fun getLayoutId() = R.layout.fragment_news_details override - fun setUpViews() { + fun getFragmentArguments() { getArticleFromArguments() + } + + override + fun setBindingVariables() { + binding.viewModel = viewModel + binding.article = article + } + + override + fun setUpViews() { + showLoading() setUpWebView() } @@ -39,7 +52,8 @@ class NewsDetailsFragment : BaseFragment() { @SuppressLint("SetJavaScriptEnabled") private fun setUpWebView() { binding.webView.apply { - webViewClient = WebViewClient() + webChromeClient = WebChromeClient() + webViewClient = getLollipopWebViewClient() settings.javaScriptEnabled = true loadUrl(article.url) @@ -47,10 +61,26 @@ class NewsDetailsFragment : BaseFragment() { } override - fun handleClickListeners() { - binding.btnSaveArticle.setOnClickListener { - viewModel.saveArticleToLocal(article) + fun setupObservers() { + viewModel.onArticleSavedToLocal.observe(this, { showMessage(resources.getString(R.string.article_saved_to_local)) + }) + } + + @RequiresApi(api = VERSION_CODES.LOLLIPOP) private fun getLollipopWebViewClient(): WebViewClient { + return object : WebViewClient() { + override + fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean { + showLoading() + val url = request.url.toString() + binding.webView.loadUrl(url) + return true + } + + override + fun onPageFinished(view: WebView, url: String) { + hideLoading() + } } } } \ No newline at end of file diff --git a/app/src/main/java/com/mina_mikhail/newsapp/features/news/presentation/news_details/NewsDetailsViewModel.kt b/app/src/main/java/com/mina_mikhail/newsapp/features/news/presentation/news_details/NewsDetailsViewModel.kt index 667e6a1..e9ade26 100644 --- a/app/src/main/java/com/mina_mikhail/newsapp/features/news/presentation/news_details/NewsDetailsViewModel.kt +++ b/app/src/main/java/com/mina_mikhail/newsapp/features/news/presentation/news_details/NewsDetailsViewModel.kt @@ -1,7 +1,8 @@ package com.mina_mikhail.newsapp.features.news.presentation.news_details -import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.mina_mikhail.newsapp.core.utils.SingleLiveEvent +import com.mina_mikhail.newsapp.core.view.BaseViewModel import com.mina_mikhail.newsapp.features.news.data.repository.NewsRepository import com.mina_mikhail.newsapp.features.news.domain.entity.model.Article import dagger.hilt.android.lifecycle.HiltViewModel @@ -9,10 +10,16 @@ import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel -class NewsDetailsViewModel @Inject constructor(private val repository: NewsRepository) : ViewModel() { +class NewsDetailsViewModel @Inject constructor(private val repository: NewsRepository) : BaseViewModel() { - fun saveArticleToLocal(article: Article) = viewModelScope.launch { + val onArticleSavedToLocal: SingleLiveEvent = SingleLiveEvent() + + private fun saveArticleToLocal(article: Article) = viewModelScope.launch { repository.saveArticleToLocal(article) } + fun saveArticle(article: Article) { + saveArticleToLocal(article) + onArticleSavedToLocal.call() + } } \ No newline at end of file diff --git a/app/src/main/java/com/mina_mikhail/newsapp/features/news/presentation/saved_news/SavedNewsFragment.kt b/app/src/main/java/com/mina_mikhail/newsapp/features/news/presentation/saved_news/SavedNewsFragment.kt index 8bde569..279c794 100644 --- a/app/src/main/java/com/mina_mikhail/newsapp/features/news/presentation/saved_news/SavedNewsFragment.kt +++ b/app/src/main/java/com/mina_mikhail/newsapp/features/news/presentation/saved_news/SavedNewsFragment.kt @@ -1,8 +1,6 @@ package com.mina_mikhail.newsapp.features.news.presentation.saved_news import android.os.Bundle -import android.view.LayoutInflater -import android.view.ViewGroup import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.ItemTouchHelper @@ -25,8 +23,7 @@ class SavedNewsFragment : BaseFragment() { private lateinit var articlesAdapter: NewsAdapter override - fun getViewBinding(inflater: LayoutInflater, container: ViewGroup?) = - FragmentSavedNewsBinding.inflate(inflater, container, false) + fun getLayoutId() = R.layout.fragment_saved_news override fun setUpViews() { diff --git a/app/src/main/java/com/mina_mikhail/newsapp/features/news/presentation/saved_news/SavedNewsViewModel.kt b/app/src/main/java/com/mina_mikhail/newsapp/features/news/presentation/saved_news/SavedNewsViewModel.kt index 1c86fcc..52163de 100644 --- a/app/src/main/java/com/mina_mikhail/newsapp/features/news/presentation/saved_news/SavedNewsViewModel.kt +++ b/app/src/main/java/com/mina_mikhail/newsapp/features/news/presentation/saved_news/SavedNewsViewModel.kt @@ -1,7 +1,7 @@ package com.mina_mikhail.newsapp.features.news.presentation.saved_news -import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.mina_mikhail.newsapp.core.view.BaseViewModel import com.mina_mikhail.newsapp.features.news.data.repository.NewsRepository import com.mina_mikhail.newsapp.features.news.domain.entity.model.Article import dagger.hilt.android.lifecycle.HiltViewModel @@ -9,7 +9,7 @@ import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel -class SavedNewsViewModel @Inject constructor(private val repository: NewsRepository) : ViewModel() { +class SavedNewsViewModel @Inject constructor(private val repository: NewsRepository) : BaseViewModel() { fun getArticlesFromLocal() = repository.getArticlesFromLocal() diff --git a/app/src/main/java/com/mina_mikhail/newsapp/features/news/presentation/search_news/SearchNewsFragment.kt b/app/src/main/java/com/mina_mikhail/newsapp/features/news/presentation/search_news/SearchNewsFragment.kt index 24e6d96..d432b96 100644 --- a/app/src/main/java/com/mina_mikhail/newsapp/features/news/presentation/search_news/SearchNewsFragment.kt +++ b/app/src/main/java/com/mina_mikhail/newsapp/features/news/presentation/search_news/SearchNewsFragment.kt @@ -2,8 +2,6 @@ package com.mina_mikhail.newsapp.features.news.presentation.search_news import android.os.Bundle import android.view.KeyEvent -import android.view.LayoutInflater -import android.view.ViewGroup import android.view.inputmethod.EditorInfo import android.widget.TextView import androidx.fragment.app.viewModels @@ -12,6 +10,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.mina_mikhail.newsapp.R import com.mina_mikhail.newsapp.core.data_source.BaseRemoteDataSource +import com.mina_mikhail.newsapp.core.enums.DataStatus import com.mina_mikhail.newsapp.core.network.Resource.Empty import com.mina_mikhail.newsapp.core.network.Resource.Failure import com.mina_mikhail.newsapp.core.network.Resource.Loading @@ -38,8 +37,7 @@ class SearchNewsFragment : BaseFragment() { private lateinit var searchTextListener: SearchEditTextListener override - fun getViewBinding(inflater: LayoutInflater, container: ViewGroup?) = - FragmentSearchNewsBinding.inflate(inflater, container, false) + fun getLayoutId() = R.layout.fragment_search_news override fun registerListeners() { @@ -51,6 +49,12 @@ class SearchNewsFragment : BaseFragment() { stopSearchListener() } + override + fun setBindingVariables() { + binding.viewModel = viewModel + binding.includedList.baseViewModel = viewModel + } + override fun setUpViews() { initSearchListener() @@ -132,17 +136,15 @@ class SearchNewsFragment : BaseFragment() { when (it) { is Loading -> { if (articlesAdapter.currentList.isNullOrEmpty()) { - showDataLoading() + viewModel.dataLoadingEvent.value = DataStatus.LOADING } else { - showPaginationLoading() + viewModel.dataLoadingEvent.value = DataStatus.LOADING_NEXT_PAGE } } is Empty -> { if (articlesAdapter.currentList.isNullOrEmpty()) { - showNoData() + viewModel.dataLoadingEvent.value = DataStatus.NO_DATA hideKeyboard() - } else { - hidePaginationLoading() } } is Success -> { @@ -159,15 +161,10 @@ class SearchNewsFragment : BaseFragment() { articlesAdapter.submitList(articlesAdapter.currentList + it.value.articles) } - showData() + viewModel.dataLoadingEvent.value = DataStatus.SHOW_DATA } is Failure -> { - if (articlesAdapter.currentList.isNullOrEmpty()) { - handleApiError(it, noDataAction = { showNoData() }, noInternetAction = { showNoInternet() }) - } else { - handleApiError(it) - hidePaginationLoading() - } + handleApiError(it) } } }) @@ -185,13 +182,17 @@ class SearchNewsFragment : BaseFragment() { } override - fun handleClickListeners() { - binding.btnDismissSearch.setOnClickListener { - initPagingParameters() - hideKeyboard() - binding.etSearch.setText("") - binding.etSearch.clearFocus() - } + fun setupObservers() { + viewModel.clearSearchArea.observe(this, { + clearSearchArea() + }) + } + + private fun clearSearchArea() { + initPagingParameters() + hideKeyboard() + binding.etSearch.setText("") + binding.etSearch.clearFocus() } private fun onArticleClick(article: Article) { @@ -218,45 +219,4 @@ class SearchNewsFragment : BaseFragment() { binding.btnDismissSearch.show() binding.searchHint.hide() } - - private fun showDataLoading() { - binding.includedList.container.show() - binding.includedList.progressBar.show() - binding.includedList.emptyViewContainer.hide() - binding.includedList.internetErrorViewContainer.hide() - binding.includedList.recyclerView.hide() - binding.includedList.paginationProgressBar.hide() - } - - private fun showPaginationLoading() { - binding.includedList.paginationProgressBar.show() - } - - private fun hidePaginationLoading() { - binding.includedList.paginationProgressBar.hide() - } - - private fun showData() { - binding.includedList.recyclerView.show() - binding.includedList.container.hide() - binding.includedList.paginationProgressBar.hide() - } - - private fun showNoData() { - binding.includedList.container.show() - binding.includedList.emptyViewContainer.show() - binding.includedList.internetErrorViewContainer.hide() - binding.includedList.progressBar.hide() - binding.includedList.paginationProgressBar.hide() - binding.includedList.recyclerView.hide() - } - - private fun showNoInternet() { - binding.includedList.container.show() - binding.includedList.internetErrorViewContainer.show() - binding.includedList.emptyViewContainer.hide() - binding.includedList.progressBar.hide() - binding.includedList.paginationProgressBar.hide() - binding.includedList.recyclerView.hide() - } } \ No newline at end of file diff --git a/app/src/main/java/com/mina_mikhail/newsapp/features/news/presentation/search_news/SearchNewsViewModel.kt b/app/src/main/java/com/mina_mikhail/newsapp/features/news/presentation/search_news/SearchNewsViewModel.kt index 12f1c38..5d867c6 100644 --- a/app/src/main/java/com/mina_mikhail/newsapp/features/news/presentation/search_news/SearchNewsViewModel.kt +++ b/app/src/main/java/com/mina_mikhail/newsapp/features/news/presentation/search_news/SearchNewsViewModel.kt @@ -1,23 +1,30 @@ package com.mina_mikhail.newsapp.features.news.presentation.search_news -import androidx.lifecycle.ViewModel import androidx.lifecycle.liveData import com.mina_mikhail.newsapp.core.network.Resource +import com.mina_mikhail.newsapp.core.utils.SingleLiveEvent +import com.mina_mikhail.newsapp.core.view.BaseViewModel import com.mina_mikhail.newsapp.features.news.data.repository.NewsRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import javax.inject.Inject @HiltViewModel -class SearchNewsViewModel @Inject constructor(private val repository: NewsRepository) : ViewModel() { +class SearchNewsViewModel @Inject constructor(private val repository: NewsRepository) : BaseViewModel() { var searchQuery: String = "" var shouldLoadMore = false var isLoading = false var page: Int = 1 + val clearSearchArea: SingleLiveEvent = SingleLiveEvent() + fun searchForNews() = liveData(Dispatchers.IO) { emit(Resource.Loading) emit(repository.searchForNews(searchQuery, page)) } + + fun onDismissClicked() { + clearSearchArea.call() + } } \ No newline at end of file diff --git a/app/src/main/java/com/mina_mikhail/newsapp/features/splash/presentation/SplashActivity.kt b/app/src/main/java/com/mina_mikhail/newsapp/features/splash/presentation/SplashActivity.kt index 1651189..aa91cbe 100644 --- a/app/src/main/java/com/mina_mikhail/newsapp/features/splash/presentation/SplashActivity.kt +++ b/app/src/main/java/com/mina_mikhail/newsapp/features/splash/presentation/SplashActivity.kt @@ -2,6 +2,7 @@ package com.mina_mikhail.newsapp.features.splash.presentation import android.os.Handler import android.os.Looper +import com.mina_mikhail.newsapp.R import com.mina_mikhail.newsapp.core.view.BaseActivity import com.mina_mikhail.newsapp.core.view.extensions.openActivityAndClearStack import com.mina_mikhail.newsapp.databinding.ActivitySplashBinding @@ -12,7 +13,7 @@ import dagger.hilt.android.AndroidEntryPoint class SplashActivity : BaseActivity() { override - fun getViewBinding() = ActivitySplashBinding.inflate(layoutInflater) + fun getLayoutId() = R.layout.activity_splash override fun setUpViews() { diff --git a/app/src/main/res/layout/activity_news.xml b/app/src/main/res/layout/activity_news.xml index 036f4fe..39fd83a 100644 --- a/app/src/main/res/layout/activity_news.xml +++ b/app/src/main/res/layout/activity_news.xml @@ -1,25 +1,30 @@ - - + android:layout_height="match_parent" + android:orientation="vertical" + > - + + + + + - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_splash.xml b/app/src/main/res/layout/activity_splash.xml index ce36ca2..bae1942 100644 --- a/app/src/main/res/layout/activity_splash.xml +++ b/app/src/main/res/layout/activity_splash.xml @@ -1,16 +1,21 @@ - - + - \ No newline at end of file + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_breaking_news.xml b/app/src/main/res/layout/fragment_breaking_news.xml index b60dbd1..c2f82a1 100644 --- a/app/src/main/res/layout/fragment_breaking_news.xml +++ b/app/src/main/res/layout/fragment_breaking_news.xml @@ -1,23 +1,27 @@ - + - - + - + - \ No newline at end of file + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_news_details.xml b/app/src/main/res/layout/fragment_news_details.xml index b510dab..7e2bb27 100644 --- a/app/src/main/res/layout/fragment_news_details.xml +++ b/app/src/main/res/layout/fragment_news_details.xml @@ -1,28 +1,47 @@ - - + + + + + + + + - - - - \ No newline at end of file + > + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_saved_news.xml b/app/src/main/res/layout/fragment_saved_news.xml index a2d5330..259badd 100644 --- a/app/src/main/res/layout/fragment_saved_news.xml +++ b/app/src/main/res/layout/fragment_saved_news.xml @@ -1,19 +1,23 @@ - + - + android:layout_height="match_parent" + android:gravity="center" + android:orientation="vertical" + > - \ No newline at end of file + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_search_news.xml b/app/src/main/res/layout/fragment_search_news.xml index 1dd80a1..3f5ea60 100644 --- a/app/src/main/res/layout/fragment_search_news.xml +++ b/app/src/main/res/layout/fragment_search_news.xml @@ -1,108 +1,123 @@ - - - - - - + - - + - - - + + + + + + + + + + + android:orientation="vertical" + android:padding="@dimen/activity_padding" + > - + - + - + + + + + - + + + - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/item_news.xml b/app/src/main/res/layout/item_news.xml index 4f3cdc6..ebb8a7c 100644 --- a/app/src/main/res/layout/item_news.xml +++ b/app/src/main/res/layout/item_news.xml @@ -1,82 +1,87 @@ - - - - - - + android:layout_marginStart="@dimen/dimen10" + android:layout_weight="1" + android:orientation="vertical" + > - + + + + + + + - + - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/list_general.xml b/app/src/main/res/layout/list_general.xml index 3ed2453..2d78994 100644 --- a/app/src/main/res/layout/list_general.xml +++ b/app/src/main/res/layout/list_general.xml @@ -1,14 +1,21 @@ - + + + + + + + + @@ -71,42 +74,40 @@ android:layout_margin="@dimen/dimen6" android:gravity="center" android:text="@string/no_internet" - android:textColor="@color/darkGray" + android:textColor="@color/black" /> + + + + - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/toast.xml b/app/src/main/res/layout/toast.xml deleted file mode 100644 index 2672c1e..0000000 --- a/app/src/main/res/layout/toast.xml +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 6ae63f2..1966805 100644 --- a/gradle.properties +++ b/gradle.properties @@ -19,4 +19,5 @@ android.useAndroidX=true android.enableJetifier=true # Kotlin code style for this project: "official" or "obsolete": kotlin.code.style=official -android.enableR8=true \ No newline at end of file +android.enableR8=true +android.databinding.enableV2=true \ No newline at end of file