diff --git a/app/src/main/java/com/denizk0461/weserplaner/adapter/CanteenOfferItemAdapter.kt b/app/src/main/java/com/denizk0461/weserplaner/adapter/CanteenOfferItemAdapter.kt index ebd674b..fa39276 100644 --- a/app/src/main/java/com/denizk0461/weserplaner/adapter/CanteenOfferItemAdapter.kt +++ b/app/src/main/java/com/denizk0461/weserplaner/adapter/CanteenOfferItemAdapter.kt @@ -2,6 +2,7 @@ package com.denizk0461.weserplaner.adapter import android.content.res.ColorStateList import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup import androidx.appcompat.content.res.AppCompatResources import androidx.recyclerview.widget.DiffUtil @@ -29,8 +30,16 @@ class CanteenOfferItemAdapter( private val displayColours: Boolean, ) : RecyclerView.Adapter() { + /** + * Offers to display. + */ private val offers: MutableList = mutableListOf() + /** + * Difference between filtered list and the list fetched from the website. + */ + private var difference: Int = 0 + /** * View holder class for parent class * @@ -43,7 +52,7 @@ class CanteenOfferItemAdapter( ItemCanteenBinding.inflate( LayoutInflater.from(parent.context), parent, - false + false, ) ) } @@ -52,9 +61,36 @@ class CanteenOfferItemAdapter( override fun getItemCount(): Int = offers.size override fun onBindViewHolder(holder: OfferViewHolder, position: Int) { + // Retrieve item for current position val currentItem = offers[position] + // Context for easier reference + val context = holder.binding.root.context + + holder.binding.canteenCard.visibility = if (currentItem.date == "ALL\$HIDDEN") { + View.GONE + } else { + View.VISIBLE + } + + // Display + if (position == (offers.size - 1) && difference > 0) { + holder.binding.textDifference.apply { + visibility = View.VISIBLE + text = if (difference == 1) { + context.getString(R.string.canteen_item_difference_text_one) + } else { + context.getString(R.string.canteen_item_difference_text_other, difference) + } + } + } else { + holder.binding.textDifference.apply { + visibility = View.GONE + text = "" + } + } + /* * Children of the line container MUST be removed before any are added, as otherwise, during * recycling, lines will be duplicated when they are scrolled off the visible screen area @@ -69,34 +105,36 @@ class CanteenOfferItemAdapter( // Set the text to "No offers" (localised) holder.binding.textCategory.text = - holder.binding.root.context.getString(R.string.canteen_no_offer_header) + context.getString(R.string.canteen_no_offer_header) // Inflate a single line without binding it to the line container val line = ItemCanteenLineBinding.inflate( - LayoutInflater.from(holder.binding.root.context), + LayoutInflater.from(context), ) // Set content to tell the user that no offers are available (localised) line.textContent.text = - holder.binding.root.context.getString(R.string.canteen_no_offer_desc) + context.getString(R.string.canteen_no_offer_desc) // Inflate a new icon holder - val img = ItemIconBinding.inflate(LayoutInflater.from(holder.binding.root.context)) + val img = ItemIconBinding.inflate(LayoutInflater.from(context)) // Retrieve data for the dietary preference - val (_, drawableId, colourId) = DietaryPreferences.getData(DietaryPreferences.ERROR.ordinal) + val (_, drawableId, colourId) = DietaryPreferences.getData( + DietaryPreferences.ERROR.ordinal, + ) // Set a cross icon img.imageView.setImageDrawable( AppCompatResources.getDrawable( - holder.binding.root.context, + context, drawableId, ) ) // Set tint of the icon; themed to the preference, if the user has the option enabled img.imageView.imageTintList = ColorStateList.valueOf( - holder.binding.root.context.theme.getThemedColor( + context.theme.getThemedColor( if (displayColours) { colourId } else { @@ -119,7 +157,7 @@ class CanteenOfferItemAdapter( currentItem.offers.forEach { offer -> // Inflate the new line without binding it to the line container val line = ItemCanteenLineBinding.inflate( - LayoutInflater.from(holder.binding.root.context), + LayoutInflater.from(context), ) // Set text values @@ -144,7 +182,7 @@ class CanteenOfferItemAdapter( // Iterate through all icons that need to be displayed indices.forEach { index -> // Inflate a new icon holder - val img = ItemIconBinding.inflate(LayoutInflater.from(holder.binding.root.context)) + val img = ItemIconBinding.inflate(LayoutInflater.from(context)) // Retrieve data for the dietary preference val (_, drawableId, colourId) = DietaryPreferences.getData(index) @@ -152,14 +190,14 @@ class CanteenOfferItemAdapter( // Set the appropriate icon img.imageView.setImageDrawable( AppCompatResources.getDrawable( - holder.binding.root.context, + context, drawableId, ) ) // Set tint of the icon img.imageView.imageTintList = ColorStateList.valueOf( - holder.binding.root.context.theme.getThemedColor( + context.theme.getThemedColor( if (displayColours) { colourId } else { @@ -193,7 +231,9 @@ class CanteenOfferItemAdapter( * * @param newData new dataset to be displayed */ - fun setNewData(newData: List) { + fun setNewData(newData: List, difference: Int) { + this.difference = difference + // Calculate the difference between the old list and the new list val diffResult = DiffUtil.calculateDiff(AppDiffUtilCallback(offers, newData)) @@ -205,6 +245,9 @@ class CanteenOfferItemAdapter( // Tell the DiffUtil which items have changed between the two lists diffResult.dispatchUpdatesTo(this) + + // Force an update on the last item to display how many items have been hidden + notifyItemChanged(offers.size - 1) } /** diff --git a/app/src/main/java/com/denizk0461/weserplaner/db/AppRepository.kt b/app/src/main/java/com/denizk0461/weserplaner/db/AppRepository.kt index d6120a4..5d8ce7f 100644 --- a/app/src/main/java/com/denizk0461/weserplaner/db/AppRepository.kt +++ b/app/src/main/java/com/denizk0461/weserplaner/db/AppRepository.kt @@ -263,7 +263,7 @@ class AppRepository(app: Application) { defaultValue = AllergenPreferences.TEMPLATE, ) /** - * This value determines how many allergens the user wants to have displayed or hidden. + * This value determines how many allergens the user wants to have hidden. */ fun getPreferenceAllergenConfigCount(): Int = getPreferenceAllergenConfig().run { if (isBlank()) 0 else split(",").count() diff --git a/app/src/main/java/com/denizk0461/weserplaner/fragment/CanteenPageFragment.kt b/app/src/main/java/com/denizk0461/weserplaner/fragment/CanteenPageFragment.kt index 8d1a489..2b1081a 100644 --- a/app/src/main/java/com/denizk0461/weserplaner/fragment/CanteenPageFragment.kt +++ b/app/src/main/java/com/denizk0461/weserplaner/fragment/CanteenPageFragment.kt @@ -84,36 +84,75 @@ class CanteenPageFragment : AppFragment(), CanteenOfferItemAdapter.OnClickListen // Observe offers and re-set them if they change viewModel.getOffersByDay(currentDay).observe(viewLifecycleOwner) { offers -> + // Refresh temporarily stored offer list offerList.clear() offerList.addAll(offers) - recyclerViewAdapter.setNewData(offers.filterElements().groupElements().distinct()) + + // Set new data + recyclerViewAdapter.setData(offers) } } - // Retrieve parent fragment to access its functions - val navHostFragment = activity?.supportFragmentManager?.fragments?.get(0) as? NavHostFragment - val parent = navHostFragment?.childFragmentManager?.primaryNavigationFragment as? CanteenFragment + // Set up FAB behaviour for parent fragment's FAB if activity is not null + activity?.let { activity -> + // Retrieve parent fragment to access its functions + val navHostFragment = activity + .supportFragmentManager + .fragments[0] as NavHostFragment + val parentFragment = navHostFragment + .childFragmentManager + .primaryNavigationFragment as CanteenFragment - // Set up scroll change listener to shrink and extend FAB accordingly - binding.recyclerView.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY -> - // Calculate the vertical scroll difference - val dy = scrollY - oldScrollY - if (dy > 0) { - // If scrolling down, shrink the FAB - parent?.shrinkFab() - } else if (dy < 0) { - // If scrolling up, extend the FAB - parent?.extendFab() + // Set up scroll change listener to shrink and extend FAB accordingly + binding.recyclerView.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY -> + // Calculate the vertical scroll difference + val dy = scrollY - oldScrollY + if (dy > 0) { + // If scrolling down, shrink the FAB + parentFragment.shrinkFab() + } else if (dy < 0) { + // If scrolling up, extend the FAB + parentFragment.extendFab() + } } } - // Observe dietary preference updates and re-set the items if a change is detected + /* + * Observe dietary preference updates and re-set the items if a change is detected to force + * an update. + */ viewModel.dietaryPreferencesUpdate.observe(viewLifecycleOwner) { - // Re-set the list of items to the recycler view, forcing an update - recyclerViewAdapter.setNewData(offerList.filterElements().groupElements().distinct()) + // Re-set data + recyclerViewAdapter.setData(offerList) } } + private fun CanteenOfferItemAdapter.setData(offers: List) { + /* + * Filter items that contain user-selected allergens or user-excluded dietary + * preferences. + */ + val filteredOffers = offers.filterElements() + + // Put elements into groups + val groupedOffers = filteredOffers.groupElements().distinct() + + val difference = offers.size - filteredOffers.size + + val newOffers = if (groupedOffers.isEmpty() && difference > 0) { + listOf(CanteenOfferGroup( + "ALL\$HIDDEN", 0, "", "", listOf(), + )) + } else groupedOffers + + // Set new items into the adapter + setNewData( + newOffers, + // Difference is calculated from the unfiltered and filtered data sets + difference, + ) + } + /** * Groups [CanteenOffer] elements by their categories into [CanteenOfferGroup] elements and * filters for the user's dietary preferences. @@ -130,7 +169,7 @@ class CanteenPageFragment : AppFragment(), CanteenOfferItemAdapter.OnClickListen category, canteen, filter { - it.category == category && it.date == date && it.canteen == canteen + it.category == category && it.canteen == canteen }.map { // Map the individual items to their respective groups CanteenOfferGroupElement( diff --git a/app/src/main/res/layout/item_canteen.xml b/app/src/main/res/layout/item_canteen.xml index 6be66a5..5fe16be 100644 --- a/app/src/main/res/layout/item_canteen.xml +++ b/app/src/main/res/layout/item_canteen.xml @@ -4,9 +4,11 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingHorizontal="8dp" - android:paddingVertical="4dp"> + android:paddingVertical="4dp" + android:orientation="vertical"> @@ -45,4 +47,10 @@ + + \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 3e06f69..b6e38a4 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -24,6 +24,9 @@ Du musst deinen Stundenplan öffnen! Mensaplan konnte nicht heruntergeladen werden! + Ein Angebot aufgrund deiner Präferenzen versteckt. + %d Angebote aufgrund deiner Präferenzen versteckt. + Lade deinen Stundenplan von Stud.IP herunter diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 887c978..4433bed 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -25,6 +25,9 @@ You must navigate to your schedule! Couldn\'t retrieve the canteen plan! + One offer hidden due to your preferences. + %d offers hidden due to your preferences. + Download your schedule from Stud.IP