diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
new file mode 100644
index 0000000..586195f
--- /dev/null
+++ b/.idea/deploymentTargetSelector.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/it/bz/noi/community/data/models/News.kt b/app/src/main/java/it/bz/noi/community/data/models/News.kt
index 0ea8440..2ad972f 100644
--- a/app/src/main/java/it/bz/noi/community/data/models/News.kt
+++ b/app/src/main/java/it/bz/noi/community/data/models/News.kt
@@ -11,6 +11,8 @@ import it.bz.noi.community.utils.Utils
import kotlinx.parcelize.Parcelize
import java.util.*
+private const val ID_TAG_IMPORTANT = "important" // ID of the "important" tag
+
@Keep
@Parcelize
data class News(
@@ -27,7 +29,7 @@ data class News(
@SerializedName("ODHTags")
val tags: List? = null,
@SerializedName("Highlight")
- val highlighted: Boolean = false,
+ val isHighlighted: Boolean = false,
) : Parcelable
@Keep
@@ -68,21 +70,21 @@ data class NewsImage(
val url: String? = null
) : Parcelable
-fun News.getDetail(): Detail? {
- return detail[Utils.getAppLanguage()]
-}
+/**
+ * Get the localized detail for the current app language.
+ */
+fun News.getLocalizedDetail(): Detail? = detail[Utils.getAppLanguage()]
-fun News.getContactInfo(): ContactInfo? {
- return contactInfo?.let {
- it[Utils.getAppLanguage()]
- }
-}
+/**
+ * Get the localized contact info for the current app language.
+ */
+fun News.getLocalizedContactInfo(): ContactInfo? = contactInfo?.get(Utils.getAppLanguage())
// FIXME -> WIP
-fun News.hasImportantFlag(): Boolean {
- val isImportant = tags != null && tags.filter { it.id == "important" }.isNotEmpty()
- return isImportant || highlighted
-}
+/**
+ * Whether the news is important, that is, it has the "important" tag.
+ */
+val News.isImportant get(): Boolean = tags?.any { it.id == ID_TAG_IMPORTANT } ?: false
data class NewsResponse(
@SerializedName("TotalResults")
diff --git a/app/src/main/java/it/bz/noi/community/data/models/NewsParams.kt b/app/src/main/java/it/bz/noi/community/data/models/NewsParams.kt
index 044cc45..bbe3afb 100644
--- a/app/src/main/java/it/bz/noi/community/data/models/NewsParams.kt
+++ b/app/src/main/java/it/bz/noi/community/data/models/NewsParams.kt
@@ -4,6 +4,10 @@
package it.bz.noi.community.data.models
+import it.bz.noi.community.utils.DateUtils
+import it.bz.noi.community.utils.Utils
+import java.util.Date
+
data class NewsParams(
var startDate: String,
var pageSize: Int = 10,
@@ -12,8 +16,17 @@ data class NewsParams(
var highlight: Boolean = false
)
-fun NewsParams.getRawFilter(): String {
- if (highlight)
- return "eq(Highlight,\"true\")"
- return "or(eq(Highlight,\"false\"),isnull(Highlight))"
-}
+/**
+ * Factory for creating [NewsParams].
+ */
+fun NewsParams(nextPageNumber: Int, pageSize: Int, from: Date, moreHighlights: Boolean) =
+ NewsParams(
+ startDate = DateUtils.parameterDateFormatter().format(from),
+ pageSize = pageSize,
+ pageNumber = nextPageNumber,
+ language = Utils.getAppLanguage(),
+ highlight = moreHighlights
+ )
+
+fun NewsParams.getRawFilter(): String =
+ if (highlight) "eq(Highlight,\"true\")" else "or(eq(Highlight,\"false\"),isnull(Highlight))"
diff --git a/app/src/main/java/it/bz/noi/community/ui/MainViewModel.kt b/app/src/main/java/it/bz/noi/community/ui/MainViewModel.kt
index 28deea7..575e141 100644
--- a/app/src/main/java/it/bz/noi/community/ui/MainViewModel.kt
+++ b/app/src/main/java/it/bz/noi/community/ui/MainViewModel.kt
@@ -33,6 +33,8 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.*
import java.util.*
+private const val PAGE_SIZE = 10 // How many news to load at once
+
/**
* Factory for creating the MainViewModel
*/
@@ -242,7 +244,7 @@ class MainViewModel(
filters= filterRepo.loadFilters()
}
- if (filters != null && filters.isNotEmpty())
+ if (filters.isNotEmpty())
emit(Resource.success(data = filters))
else
emit(Resource.error(data = null, message = "Filter loading: error occurred!"))
@@ -256,8 +258,8 @@ class MainViewModel(
loadNews()
}.stateIn(viewModelScope, SharingStarted.Lazily, PagingData.empty())
- private fun loadNews(): Flow> = Pager(PagingConfig(pageSize = NewsPagingSource.PAGE_ITEMS)) {
- NewsPagingSource(mainRepository)
+ private fun loadNews(): Flow> = Pager(PagingConfig(pageSize = PAGE_SIZE)) {
+ NewsPagingSource(PAGE_SIZE, mainRepository)
}.flow.cachedIn(viewModelScope)
fun refreshNews() {
@@ -267,9 +269,8 @@ class MainViewModel(
}
object NewsTickerFlow {
- val ticker = MutableSharedFlow(replay = 1).apply {
+ private val ticker = MutableSharedFlow(replay = 1).apply {
tryEmit(Unit)
}
fun tick() = ticker.tryEmit(Unit)
}
-
diff --git a/app/src/main/java/it/bz/noi/community/ui/newsDetails/NewsDetailsFragment.kt b/app/src/main/java/it/bz/noi/community/ui/newsDetails/NewsDetailsFragment.kt
index 1069263..cdcd491 100644
--- a/app/src/main/java/it/bz/noi/community/ui/newsDetails/NewsDetailsFragment.kt
+++ b/app/src/main/java/it/bz/noi/community/ui/newsDetails/NewsDetailsFragment.kt
@@ -32,8 +32,8 @@ import it.bz.noi.community.data.api.ApiHelper
import it.bz.noi.community.data.api.RetrofitBuilder
import it.bz.noi.community.data.models.News
import it.bz.noi.community.data.models.NewsImage
-import it.bz.noi.community.data.models.getContactInfo
-import it.bz.noi.community.data.models.getDetail
+import it.bz.noi.community.data.models.getLocalizedContactInfo
+import it.bz.noi.community.data.models.getLocalizedDetail
import it.bz.noi.community.databinding.FragmentNewsDetailsBinding
import it.bz.noi.community.databinding.VhHorizontalImageBinding
import it.bz.noi.community.utils.Status
@@ -41,17 +41,22 @@ import kotlinx.coroutines.Dispatchers
import java.text.DateFormat
-class NewsDetailsFragment: Fragment() {
+class NewsDetailsFragment : Fragment() {
private var _binding: FragmentNewsDetailsBinding? = null
private val binding get() = _binding!!
private val viewModel: NewsDetailViewModel by viewModels(factoryProducer = {
- NewsDetailViewModelFactory(apiHelper = ApiHelper(RetrofitBuilder.opendatahubApiService, RetrofitBuilder.communityApiService),
- this@NewsDetailsFragment)
+ NewsDetailViewModelFactory(
+ apiHelper = ApiHelper(
+ RetrofitBuilder.opendatahubApiService,
+ RetrofitBuilder.communityApiService
+ ),
+ this@NewsDetailsFragment
+ )
})
- private val df = DateFormat.getDateInstance(DateFormat.SHORT)
+ private val dateFormat = DateFormat.getDateInstance(DateFormat.SHORT)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -82,16 +87,18 @@ class NewsDetailsFragment: Fragment() {
super.onViewCreated(view, savedInstanceState)
viewModel.newsFlow.asLiveData(Dispatchers.Main).observe(viewLifecycleOwner) {
- when(it.status) {
+ when (it.status) {
Status.SUCCESS -> {
binding.newsLoader.isVisible = false
val news = it.data!!
loadNewsData(news)
}
+
Status.ERROR -> {
binding.newsLoader.isVisible = false
Toast.makeText(requireContext(), it.message, Toast.LENGTH_LONG).show()
}
+
Status.LOADING -> {
binding.newsLoader.isVisible = true
}
@@ -103,22 +110,23 @@ class NewsDetailsFragment: Fragment() {
private fun loadNewsData(news: News) {
setTransitionNames(news.id)
- binding.date.text = df.format(news.date)
+ binding.date.text = dateFormat.format(news.date)
- news.getDetail()?.let { detail ->
+ news.getLocalizedDetail()?.let { detail ->
(requireActivity() as AppCompatActivity).supportActionBar?.title = detail.title
binding.title.text = detail.title
binding.shortText.text = detail.abstract
- binding.longText.text = detail.text?.let{ Html.fromHtml(it, Html.FROM_HTML_MODE_LEGACY) }
+ binding.longText.text =
+ detail.text?.let { Html.fromHtml(it, Html.FROM_HTML_MODE_LEGACY) }
binding.longText.movementMethod = LinkMovementMethod.getInstance()
}
var isExternalLink = false
var isEmail = false
- val contactInfo = news.getContactInfo()
+ val contactInfo = news.getLocalizedContactInfo()
if (contactInfo == null) {
- binding.publisher.text = "N/D"
+ binding.publisher.text = "N/D" //TODO: localize this
} else {
binding.publisher.text = contactInfo.publisher
@@ -169,9 +177,10 @@ class NewsDetailsFragment: Fragment() {
binding.askQuestion.isVisible = isEmail
binding.footer.isVisible = isExternalLink || isEmail
- if (news.images != null && news.images.isNotEmpty()) {
+ if (!news.images.isNullOrEmpty()) {
binding.images.isVisible = true
- binding.images.layoutManager = LinearLayoutManager(binding.root.context, LinearLayoutManager.HORIZONTAL, false)
+ binding.images.layoutManager =
+ LinearLayoutManager(binding.root.context, LinearLayoutManager.HORIZONTAL, false)
binding.images.adapter = NewsImagesAdapter(news.images)
} else {
binding.images.isVisible = false
@@ -190,7 +199,7 @@ class NewsDetailsFragment: Fragment() {
private fun writeEmail(receiverAddress: String) {
val intent = Intent(Intent.ACTION_SENDTO).apply {
data = Uri.parse("mailto:") // only email apps should handle this
- putExtra(Intent.EXTRA_EMAIL, Array(1) {receiverAddress})
+ putExtra(Intent.EXTRA_EMAIL, Array(1) { receiverAddress })
}
if (intent.resolveActivity(requireContext().packageManager) != null) {
startActivity(intent)
@@ -198,25 +207,27 @@ class NewsDetailsFragment: Fragment() {
}
private fun openExternalLink(url: String) {
- val intent = Intent(Intent.ACTION_VIEW).apply {
- data = Uri.parse(url)
- }
+ val intent =
+ Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, Intent.CATEGORY_APP_BROWSER).apply {
+ data = Uri.parse(url)
+ }
if (intent.resolveActivity(requireContext().packageManager) != null) {
startActivity(intent)
}
}
-
}
/**
* Adapter used to populate the image gallery of news detail
*/
-class NewsImagesAdapter(private val images: List) : RecyclerView.Adapter() {
+class NewsImagesAdapter(private val images: List) :
+ RecyclerView.Adapter() {
/**
* View holder of a single picture
*/
- inner class NewsImageViewHolder(private val binding: VhHorizontalImageBinding) : RecyclerView.ViewHolder(binding.root) {
+ inner class NewsImageViewHolder(private val binding: VhHorizontalImageBinding) :
+ RecyclerView.ViewHolder(binding.root) {
fun bind(image: NewsImage) {
Glide
@@ -228,7 +239,13 @@ class NewsImagesAdapter(private val images: List) : RecyclerView.Adap
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NewsImageViewHolder {
- return NewsImageViewHolder(VhHorizontalImageBinding.inflate(LayoutInflater.from(parent.context), parent, false))
+ return NewsImageViewHolder(
+ VhHorizontalImageBinding.inflate(
+ LayoutInflater.from(parent.context),
+ parent,
+ false
+ )
+ )
}
override fun onBindViewHolder(holder: NewsImageViewHolder, position: Int) {
diff --git a/app/src/main/java/it/bz/noi/community/ui/today/news/NewsFragment.kt b/app/src/main/java/it/bz/noi/community/ui/today/news/NewsFragment.kt
index 47fd6a4..0d122e0 100644
--- a/app/src/main/java/it/bz/noi/community/ui/today/news/NewsFragment.kt
+++ b/app/src/main/java/it/bz/noi/community/ui/today/news/NewsFragment.kt
@@ -30,9 +30,9 @@ import com.bumptech.glide.Glide
import it.bz.noi.community.data.api.ApiHelper
import it.bz.noi.community.data.api.RetrofitBuilder
import it.bz.noi.community.data.models.News
-import it.bz.noi.community.data.models.getContactInfo
-import it.bz.noi.community.data.models.getDetail
-import it.bz.noi.community.data.models.hasImportantFlag
+import it.bz.noi.community.data.models.getLocalizedContactInfo
+import it.bz.noi.community.data.models.getLocalizedDetail
+import it.bz.noi.community.data.models.isImportant
import it.bz.noi.community.data.repository.JsonFilterRepository
import it.bz.noi.community.databinding.FragmentNewsBinding
import it.bz.noi.community.databinding.VhNewsBinding
@@ -197,14 +197,16 @@ class NewsVH(private val binding: VhNewsBinding, detailListener: NewsDetailListe
fun bind(news: News) {
this.news = news
binding.date.text = df.format(news.date)
- binding.importantTag.isVisible = news.hasImportantFlag()
- if (news.highlighted) // FIXME -> WIP
+ binding.importantTag.isVisible = news.isImportant || news.isHighlighted
+ if (news.isHighlighted) { // FIXME -> WIP
binding.importantTag.text = "TMP PINNATO"
- news.getDetail()?.let { detail ->
+ }
+
+ news.getLocalizedDetail()?.let { detail ->
binding.title.text = detail.title
binding.shortText.text = detail.abstract
}
- val contactInfo = news.getContactInfo()
+ val contactInfo = news.getLocalizedContactInfo()
if (contactInfo == null) {
binding.publisher.text = "N/D"
} else {
diff --git a/app/src/main/java/it/bz/noi/community/ui/today/news/NewsPagingSource.kt b/app/src/main/java/it/bz/noi/community/ui/today/news/NewsPagingSource.kt
index 9a0e7fe..c6e73c8 100644
--- a/app/src/main/java/it/bz/noi/community/ui/today/news/NewsPagingSource.kt
+++ b/app/src/main/java/it/bz/noi/community/ui/today/news/NewsPagingSource.kt
@@ -15,22 +15,20 @@ import it.bz.noi.community.utils.DateUtils
import it.bz.noi.community.utils.Utils
import java.util.*
-class NewsPagingSource(private val mainRepository: MainRepository) :
- PagingSource() {
-
- companion object {
- private const val TAG = "NewsPagingSource"
- const val PAGE_ITEMS = 10
- }
+private const val TAG = "NewsPagingSource"
+class NewsPagingSource(private val pageSize: Int, private val mainRepository: MainRepository) :
+ PagingSource() {
+
private val startDate = Date()
+
private var moreHighlight = true
override suspend fun load(params: LoadParams): LoadResult {
// Start refresh at page 1 if undefined.
val nextPageNumber = params.key ?: 1
- val newsParams = getNewsParams(nextPageNumber)
+ val newsParams = NewsParams(nextPageNumber, pageSize, startDate, moreHighlight)
return try {
val newsResponse = mainRepository.getNews(newsParams)
@@ -42,7 +40,12 @@ class NewsPagingSource(private val mainRepository: MainRepository) :
if (moreHighlight && nextKey == null) {
moreHighlight = false
- val notHighlightedNewsParams = getNewsParams(nextPageNumber)
+ val notHighlightedNewsParams = NewsParams(
+ nextPageNumber,
+ pageSize,
+ startDate,
+ false
+ )
val notHighlightedNewsResponse = mainRepository.getNews(notHighlightedNewsParams)
news.addAll(notHighlightedNewsResponse.news)
@@ -59,17 +62,8 @@ class NewsPagingSource(private val mainRepository: MainRepository) :
FirebaseCrashlytics.getInstance().recordException(ex)
LoadResult.Error(ex)
}
-
}
- private fun getNewsParams(nextPageNumber: Int) = NewsParams(
- startDate = DateUtils.parameterDateFormatter().format(startDate),
- pageSize = PAGE_ITEMS,
- pageNumber = nextPageNumber,
- language = Utils.getAppLanguage(),
- highlight = moreHighlight
- )
-
override fun getRefreshKey(state: PagingState): Int? {
// Try to find the page key of the closest page to anchorPosition, from
// either the prevKey or the nextKey, but you need to handle nullability