Archived
Edited canteen views to have direct database access in the CanteenPageFragment.kt, allowing for filtering the individual days through the database; filtering now works (with changes animated!!) through updating a LiveData and observing it in the page fragment
This commit is contained in:
@@ -3,8 +3,10 @@ package com.denizk0461.studip.adapter
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.content.res.AppCompatResources
|
import androidx.appcompat.content.res.AppCompatResources
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.denizk0461.studip.R
|
import com.denizk0461.studip.R
|
||||||
|
import com.denizk0461.studip.data.AppDiffUtilCallback
|
||||||
import com.denizk0461.studip.databinding.ItemCanteenBinding
|
import com.denizk0461.studip.databinding.ItemCanteenBinding
|
||||||
import com.denizk0461.studip.databinding.ItemCanteenLineBinding
|
import com.denizk0461.studip.databinding.ItemCanteenLineBinding
|
||||||
import com.denizk0461.studip.databinding.ItemIconBinding
|
import com.denizk0461.studip.databinding.ItemIconBinding
|
||||||
@@ -15,16 +17,16 @@ import com.denizk0461.studip.model.DietaryPreferences
|
|||||||
/**
|
/**
|
||||||
* Custom RecyclerView adapter that lays out the offers of a given canteen on a given day.
|
* Custom RecyclerView adapter that lays out the offers of a given canteen on a given day.
|
||||||
*
|
*
|
||||||
* @param offers list of all offers filtered by canteen and day, grouped by category
|
|
||||||
* @param onClickListener used for listening to clicks and long presses
|
* @param onClickListener used for listening to clicks and long presses
|
||||||
* @param displayAllergens whether the user wants allergens to be marked
|
* @param displayAllergens whether the user wants allergens to be marked
|
||||||
*/
|
*/
|
||||||
class CanteenOfferItemAdapter(
|
class CanteenOfferItemAdapter(
|
||||||
private val offers: List<CanteenOfferGroup>,
|
|
||||||
private val onClickListener: OnClickListener,
|
private val onClickListener: OnClickListener,
|
||||||
private val displayAllergens: Boolean,
|
private val displayAllergens: Boolean,
|
||||||
) : RecyclerView.Adapter<CanteenOfferItemAdapter.OfferViewHolder>() {
|
) : RecyclerView.Adapter<CanteenOfferItemAdapter.OfferViewHolder>() {
|
||||||
|
|
||||||
|
private val offers: MutableList<CanteenOfferGroup> = mutableListOf()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* View holder class for parent class
|
* View holder class for parent class
|
||||||
*
|
*
|
||||||
@@ -121,7 +123,7 @@ class CanteenOfferItemAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If no dietary preferences are met, set the view to display a neutral icon
|
// If no dietary preferences are met, set the view to display a neutral icon
|
||||||
if (indices.isEmpty()) indices.add(10)
|
if (indices.isEmpty()) indices.add(DietaryPreferences.NONE.ordinal)
|
||||||
|
|
||||||
// Iterate through all icons that need to be displayed
|
// Iterate through all icons that need to be displayed
|
||||||
indices.forEach { index ->
|
indices.forEach { index ->
|
||||||
@@ -157,6 +159,26 @@ class CanteenOfferItemAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the data and calculates the difference between the old dataset and the newly provided
|
||||||
|
* dataset.
|
||||||
|
*
|
||||||
|
* @param newData new dataset to be displayed
|
||||||
|
*/
|
||||||
|
fun setNewData(newData: List<CanteenOfferGroup>) {
|
||||||
|
// Calculate the difference between the old list and the new list
|
||||||
|
val diffResult = DiffUtil.calculateDiff(AppDiffUtilCallback(offers, newData))
|
||||||
|
|
||||||
|
// Remove all items from the list
|
||||||
|
offers.clear()
|
||||||
|
|
||||||
|
// Add all items from the new list
|
||||||
|
offers.addAll(newData)
|
||||||
|
|
||||||
|
// Tell the DiffUtil which items have changed between the two lists
|
||||||
|
diffResult.dispatchUpdatesTo(this)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a star to mark allergens present in an offer. Allergens will be displayed as a star '*'
|
* Adds a star to mark allergens present in an offer. Allergens will be displayed as a star '*'
|
||||||
* next to the title.
|
* next to the title.
|
||||||
|
|||||||
@@ -4,53 +4,29 @@ import androidx.fragment.app.Fragment
|
|||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
import com.denizk0461.studip.fragment.CanteenPageFragment
|
import com.denizk0461.studip.fragment.CanteenPageFragment
|
||||||
import com.denizk0461.studip.model.CanteenOfferGroup
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom ViewPager adapter for managing multiple pages of canteen offers.
|
* Custom ViewPager adapter for managing multiple pages of canteen offers.
|
||||||
*
|
*
|
||||||
* @param fragmentActivity parent fragment activity
|
* @param fragmentActivity parent fragment activity
|
||||||
* @param offers all offers for a given canteen, grouped by category (not filtered by day at
|
|
||||||
* this point)
|
|
||||||
* @param daysCovered tells for how many days offers are available for.
|
|
||||||
* Example: if the next two weeks are available, Monday through Friday and
|
|
||||||
* excluding weekends, this value should be 10.
|
|
||||||
* @param onClickListener for managing click and long press events
|
|
||||||
* @param displayAllergens whether the user wants allergens to be marked
|
|
||||||
*/
|
*/
|
||||||
class CanteenOfferPageAdapter(
|
class CanteenOfferPageAdapter(
|
||||||
fragmentActivity: FragmentActivity,
|
fragmentActivity: FragmentActivity,
|
||||||
private var offers: List<CanteenOfferGroup>,
|
|
||||||
private var daysCovered: Int,
|
|
||||||
private val onClickListener: CanteenOfferItemAdapter.OnClickListener,
|
|
||||||
private val displayAllergens: Boolean,
|
|
||||||
) : FragmentStateAdapter(fragmentActivity) {
|
) : FragmentStateAdapter(fragmentActivity) {
|
||||||
|
|
||||||
|
private var count: Int = 0
|
||||||
|
|
||||||
// Returns the count of days covered
|
// Returns the count of days covered
|
||||||
override fun getItemCount(): Int = daysCovered
|
override fun getItemCount(): Int = count
|
||||||
|
|
||||||
override fun createFragment(position: Int): Fragment =
|
override fun createFragment(position: Int): Fragment =
|
||||||
CanteenPageFragment(
|
CanteenPageFragment(
|
||||||
offers,
|
|
||||||
onClickListener,
|
|
||||||
displayAllergens,
|
|
||||||
position,
|
position,
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
fun setItemCount(newCount: Int) {
|
||||||
* Update the entire list of items
|
count = newCount
|
||||||
*
|
|
||||||
* @param items the new set of items
|
|
||||||
*/
|
|
||||||
fun setNewItems(items: List<CanteenOfferGroup>, daysCovered: Int) {
|
|
||||||
// Update the count of days covered
|
|
||||||
this.daysCovered = daysCovered
|
|
||||||
|
|
||||||
// Update items
|
|
||||||
offers = items
|
|
||||||
|
|
||||||
// Force an update of the view. TODO replace with individual updates, as this is inefficient
|
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,7 +29,7 @@ class StudIPEventItemAdapter(
|
|||||||
/**
|
/**
|
||||||
* List of all events.
|
* List of all events.
|
||||||
*/
|
*/
|
||||||
private var events: MutableList<StudIPEvent> = mutableListOf()
|
private val events: MutableList<StudIPEvent> = mutableListOf()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve an instance of Calendar to check whether the adapter's day matches the current day.
|
* Retrieve an instance of Calendar to check whether the adapter's day matches the current day.
|
||||||
|
|||||||
@@ -240,7 +240,7 @@ class StwParser(application: Application) {
|
|||||||
categoryId,
|
categoryId,
|
||||||
title = "NO\$ITEMS",
|
title = "NO\$ITEMS",
|
||||||
price = "",
|
price = "",
|
||||||
dietaryPreferences = "..........",
|
dietaryPreferences = "tttttttttt",
|
||||||
allergens = "",
|
allergens = "",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -324,7 +324,6 @@ class StwParser(application: Application) {
|
|||||||
text = text.substring(0 until indexSupOpen) +
|
text = text.substring(0 until indexSupOpen) +
|
||||||
text.substring((indexSupClose + 6) until text.length)
|
text.substring((indexSupClose + 6) until text.length)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Return the stripped and updated HTML string and join the allergen list to a cohesive
|
* Return the stripped and updated HTML string and join the allergen list to a cohesive
|
||||||
* string without duplication.
|
* string without duplication.
|
||||||
|
|||||||
@@ -97,6 +97,30 @@ interface AppDAO {
|
|||||||
)
|
)
|
||||||
fun getOffersByPreference(prefs: String): LiveData<List<CanteenOffer>>
|
fun getOffersByPreference(prefs: String): LiveData<List<CanteenOffer>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves all canteen offers that match given day. Objects will be joined through their
|
||||||
|
* primary/foreign keys from instances of OfferDate.kt, OfferCanteen.kt, OfferCategory.kt, and
|
||||||
|
* OfferItem.kt.
|
||||||
|
*
|
||||||
|
* @return all canteen offers matching the given day exposed through a LiveData object
|
||||||
|
*/
|
||||||
|
@Query(
|
||||||
|
"SELECT * FROM offer_item " +
|
||||||
|
"JOIN offer_category ON offer_item.categoryId = offer_category.id " +
|
||||||
|
"JOIN offer_canteen ON offer_category.canteenId = offer_canteen.id " +
|
||||||
|
"JOIN offer_date ON offer_category.dateId = offer_date.id " +
|
||||||
|
"WHERE dateId = :day "
|
||||||
|
)
|
||||||
|
fun getOffersByDay(day: Int): LiveData<List<CanteenOffer>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the amount of dates represented in the offers stored locally. Can be observed.
|
||||||
|
*
|
||||||
|
* @return date count as LiveData
|
||||||
|
*/
|
||||||
|
@Query("SELECT COUNT(*) FROM offer_date")
|
||||||
|
fun getDateCount(): LiveData<Int>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves all canteen offer date objects.
|
* Retrieves all canteen offer date objects.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.denizk0461.studip.db
|
|||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.denizk0461.studip.model.*
|
import com.denizk0461.studip.model.*
|
||||||
|
|
||||||
@@ -25,6 +26,11 @@ class AppRepository(app: Application) {
|
|||||||
// Blank dietary preference regular expression
|
// Blank dietary preference regular expression
|
||||||
private val blankDietaryPrefs: String = DietaryPreferences.C_FALSE.toString().repeat(10)
|
private val blankDietaryPrefs: String = DietaryPreferences.C_FALSE.toString().repeat(10)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to observe just to register an update of the user-set dietary preferences.
|
||||||
|
*/
|
||||||
|
val dietaryPreferencesUpdate: MutableLiveData<Int> = MutableLiveData(0)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
/**
|
/**
|
||||||
* Reference to the app's repository. It is only instantiated once because it can only be
|
* Reference to the app's repository. It is only instantiated once because it can only be
|
||||||
@@ -79,6 +85,20 @@ class AppRepository(app: Application) {
|
|||||||
*/
|
*/
|
||||||
val allOffers: LiveData<List<CanteenOffer>> = dao.allOffers
|
val allOffers: LiveData<List<CanteenOffer>> = dao.allOffers
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves all canteen offers that match given day.
|
||||||
|
*
|
||||||
|
* @return all canteen offers matching the given day exposed through a LiveData object
|
||||||
|
*/
|
||||||
|
fun getOffersByDay(day: Int): LiveData<List<CanteenOffer>> = dao.getOffersByDay(day)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the amount of dates represented in the offers stored locally. Can be observed.
|
||||||
|
*
|
||||||
|
* @return date count as LiveData
|
||||||
|
*/
|
||||||
|
fun getDateCount(): LiveData<Int> = dao.getDateCount()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates a schedule element.
|
* Updates a schedule element.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -8,11 +8,9 @@ import androidx.appcompat.widget.PopupMenu
|
|||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import com.denizk0461.studip.R
|
import com.denizk0461.studip.R
|
||||||
import com.denizk0461.studip.adapter.CanteenOfferItemAdapter
|
|
||||||
import com.denizk0461.studip.adapter.CanteenOfferPageAdapter
|
import com.denizk0461.studip.adapter.CanteenOfferPageAdapter
|
||||||
import com.denizk0461.studip.databinding.FragmentCanteenBinding
|
import com.denizk0461.studip.databinding.FragmentCanteenBinding
|
||||||
import com.denizk0461.studip.model.*
|
import com.denizk0461.studip.model.*
|
||||||
import com.denizk0461.studip.sheet.AllergenSheet
|
|
||||||
import com.denizk0461.studip.sheet.TextSheet
|
import com.denizk0461.studip.sheet.TextSheet
|
||||||
import com.denizk0461.studip.viewmodel.CanteenViewModel
|
import com.denizk0461.studip.viewmodel.CanteenViewModel
|
||||||
import com.google.android.material.tabs.TabLayoutMediator
|
import com.google.android.material.tabs.TabLayoutMediator
|
||||||
@@ -21,7 +19,7 @@ import com.google.android.material.tabs.TabLayoutMediator
|
|||||||
* User-facing fragment view that displays the canteen offers from the website of the
|
* User-facing fragment view that displays the canteen offers from the website of the
|
||||||
* Studierendenwerk Bremen.
|
* Studierendenwerk Bremen.
|
||||||
*/
|
*/
|
||||||
class CanteenFragment : AppFragment(), CanteenOfferItemAdapter.OnClickListener {
|
class CanteenFragment : AppFragment() {
|
||||||
|
|
||||||
// Nullable view binding reference
|
// Nullable view binding reference
|
||||||
private var _binding: FragmentCanteenBinding? = null
|
private var _binding: FragmentCanteenBinding? = null
|
||||||
@@ -83,7 +81,7 @@ class CanteenFragment : AppFragment(), CanteenOfferItemAdapter.OnClickListener {
|
|||||||
binding.swipeRefreshLayout.isRefreshing = true
|
binding.swipeRefreshLayout.isRefreshing = true
|
||||||
|
|
||||||
// Refresh the canteen menu for the newly selected canteen
|
// Refresh the canteen menu for the newly selected canteen
|
||||||
refresh()
|
// refresh()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
inflate(R.menu.menu_canteens)
|
inflate(R.menu.menu_canteens)
|
||||||
@@ -148,40 +146,41 @@ class CanteenFragment : AppFragment(), CanteenOfferItemAdapter.OnClickListener {
|
|||||||
// Set up the view pager's adapter
|
// Set up the view pager's adapter
|
||||||
viewPagerAdapter = CanteenOfferPageAdapter(
|
viewPagerAdapter = CanteenOfferPageAdapter(
|
||||||
activity as FragmentActivity,
|
activity as FragmentActivity,
|
||||||
listOf(),
|
|
||||||
0,
|
|
||||||
this,
|
|
||||||
viewModel.preferenceAllergen,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
viewModel.getDateCount().observe(viewLifecycleOwner) { dateCount ->
|
||||||
|
viewPagerAdapter.itemCount = dateCount
|
||||||
|
binding.swipeRefreshLayout.isRefreshing = false
|
||||||
|
}
|
||||||
|
|
||||||
// Assign the adapter to the view pager
|
// Assign the adapter to the view pager
|
||||||
binding.viewPager.adapter = viewPagerAdapter
|
binding.viewPager.adapter = viewPagerAdapter
|
||||||
|
|
||||||
// Set up LiveData observer to refresh the view on update
|
// Set up LiveData observer to refresh the view on update
|
||||||
viewModel.allOffers.observe(viewLifecycleOwner) { offers ->
|
// viewModel.allOffers.observe(viewLifecycleOwner) { offers ->
|
||||||
// Update the element list stored in this fragment
|
// // Update the element list stored in this fragment
|
||||||
elements = offers
|
// elements = offers
|
||||||
|
//
|
||||||
// Group elements by category
|
// // Group elements by category
|
||||||
val groupedElements = offers.groupElements().distinct()
|
// val groupedElements = offers.groupElements().distinct()
|
||||||
|
//
|
||||||
// Find all dates for which items are available
|
// // Find all dates for which items are available
|
||||||
val newDates = groupedElements.map { it.date }.distinct()
|
// val newDates = groupedElements.map { it.date }.distinct()
|
||||||
|
//
|
||||||
// Update the date count stored in this fragment
|
// // Update the date count stored in this fragment
|
||||||
dateSize = newDates.size
|
// dateSize = newDates.size
|
||||||
|
//
|
||||||
// Update the item list in the view pager's adapter
|
// // Update the item list in the view pager's adapter
|
||||||
viewPagerAdapter.setNewItems(groupedElements, dateSize)
|
//// viewPagerAdapter.setNewItems(groupedElements, dateSize)
|
||||||
|
//
|
||||||
// Set the text for the opening hours dialogue
|
// // Set the text for the opening hours dialogue
|
||||||
openingHours = viewModel.getCanteenOpeningHours()
|
// openingHours = viewModel.getCanteenOpeningHours()
|
||||||
|
//
|
||||||
binding.swipeRefreshLayout.isRefreshing = false
|
// binding.swipeRefreshLayout.isRefreshing = false
|
||||||
|
//
|
||||||
// Create and attach the tab layout for the ViewPager
|
// // Create and attach the tab layout for the ViewPager
|
||||||
createTabLayoutMediator(newDates)
|
// createTabLayoutMediator(newDates)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Set up functions for when the user swipes to refresh the view
|
// Set up functions for when the user swipes to refresh the view
|
||||||
binding.swipeRefreshLayout.setOnRefreshListener {
|
binding.swipeRefreshLayout.setOnRefreshListener {
|
||||||
@@ -223,7 +222,8 @@ class CanteenFragment : AppFragment(), CanteenOfferItemAdapter.OnClickListener {
|
|||||||
viewModel.setPreference(pref, newValue)
|
viewModel.setPreference(pref, newValue)
|
||||||
|
|
||||||
// Update the view
|
// Update the view
|
||||||
viewPagerAdapter.setNewItems(elements.groupElements().distinct(), dateSize)
|
viewModel.registerDietaryPreferencesUpdate()
|
||||||
|
// viewPagerAdapter.setNewItems(elements.groupElements().distinct(), dateSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -235,97 +235,6 @@ class CanteenFragment : AppFragment(), CanteenOfferItemAdapter.OnClickListener {
|
|||||||
private fun getPreference(pref: DietaryPreferences): Boolean =
|
private fun getPreference(pref: DietaryPreferences): Boolean =
|
||||||
viewModel.getPreference(pref)
|
viewModel.getPreference(pref)
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the user's dietary preferences and compiles them into a regular expression that can
|
|
||||||
* be used to filter out items that don't fulfil the preferences. Example:
|
|
||||||
* .......t..|........t.
|
|
||||||
* This will retrieve all items that marked as either vegetarian or vegan
|
|
||||||
* *
|
|
||||||
* @return regular expression object reflecting the user's preferences
|
|
||||||
*/
|
|
||||||
private fun getPrefRegex(): Regex {
|
|
||||||
// Retrieve the user's dietary preference as a string
|
|
||||||
val prefs = viewModel.getDietaryPrefs().deconstruct()
|
|
||||||
|
|
||||||
// Return the 'empty' template string if no preference has been set or found
|
|
||||||
return if (prefs == emptyPreferenceRegex) {
|
|
||||||
Regex(emptyPreferenceRegex)
|
|
||||||
} else {
|
|
||||||
// Create the variable to insert the regular expression into
|
|
||||||
var regexString = ""
|
|
||||||
|
|
||||||
// Create the object to store which preferences need to be met
|
|
||||||
val indices = mutableListOf<Int>()
|
|
||||||
|
|
||||||
// Find all preferences that need to be met
|
|
||||||
prefs.forEachIndexed { index, c ->
|
|
||||||
if (c == DietaryPreferences.C_TRUE) indices.add(index)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Needs to be set to correctly assemble the regular expression
|
|
||||||
var isFirst = true
|
|
||||||
|
|
||||||
// Iterate through every preference that has been set
|
|
||||||
indices.forEach { index ->
|
|
||||||
// Set an OR if more than one preference needs to be met
|
|
||||||
if (!isFirst) regexString += '|'
|
|
||||||
|
|
||||||
// Add the expression looking for the single preference to the entire string
|
|
||||||
regexString += emptyPreferenceRegex.substring(0 until index) +
|
|
||||||
DietaryPreferences.C_TRUE +
|
|
||||||
emptyPreferenceRegex.substring(index+1)
|
|
||||||
|
|
||||||
// Ensure that OR statements will be placed between the individual expressions
|
|
||||||
isFirst = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compile the string to a regular expression object
|
|
||||||
Regex(regexString)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Groups [CanteenOffer] elements by their categories into [CanteenOfferGroup] elements and
|
|
||||||
* filters for the user's dietary preferences.
|
|
||||||
*
|
|
||||||
* @return the grouped and filtered elements
|
|
||||||
*/
|
|
||||||
private fun List<CanteenOffer>.groupElements(): List<CanteenOfferGroup> {
|
|
||||||
// Get dietary preference regex
|
|
||||||
val prefsRegex = getPrefRegex()
|
|
||||||
|
|
||||||
val filteredForPrefs = if (prefsRegex.toString() == emptyPreferenceRegex) {
|
|
||||||
// Show all elements and skip filtering if no preference is set
|
|
||||||
this
|
|
||||||
} else {
|
|
||||||
// Filter for dietary preferences
|
|
||||||
this.filter {
|
|
||||||
prefsRegex.matches(it.dietaryPreferences)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return filteredForPrefs.map { (_, date, dateId, category, _, canteen, _, _, _, _, _) ->
|
|
||||||
// Create new group elements to group the already filtered elements by their categories
|
|
||||||
CanteenOfferGroup(
|
|
||||||
date,
|
|
||||||
dateId,
|
|
||||||
category,
|
|
||||||
canteen,
|
|
||||||
filteredForPrefs.filter {
|
|
||||||
it.category == category && it.date == date && it.canteen == canteen
|
|
||||||
}.map {
|
|
||||||
// Map the individual items to their respective groups
|
|
||||||
CanteenOfferGroupElement(
|
|
||||||
it.title,
|
|
||||||
it.price,
|
|
||||||
it.dietaryPreferences,
|
|
||||||
it.allergens
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the localised string for the canteen selected by the user.
|
* Retrieves the localised string for the canteen selected by the user.
|
||||||
*
|
*
|
||||||
@@ -370,17 +279,17 @@ class CanteenFragment : AppFragment(), CanteenOfferItemAdapter.OnClickListener {
|
|||||||
*
|
*
|
||||||
* @param offer item that has been clicked
|
* @param offer item that has been clicked
|
||||||
*/
|
*/
|
||||||
override fun onClick(offer: CanteenOfferGroupElement, category: String) {
|
// override fun onClick(offer: CanteenOfferGroupElement, category: String) {
|
||||||
openBottomSheet(AllergenSheet(offer, category))
|
// openBottomSheet(AllergenSheet(offer, category))
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
/**
|
// /**
|
||||||
* Executed when an item has been long-pressed.
|
// * Executed when an item has been long-pressed.
|
||||||
*
|
// *
|
||||||
* @param offer item that has been long-pressed
|
// * @param offer item that has been long-pressed
|
||||||
* @return whether the long press was successful
|
// * @return whether the long press was successful
|
||||||
*/
|
// */
|
||||||
override fun onLongClick(offer: CanteenOfferGroupElement): Boolean {
|
// override fun onLongClick(offer: CanteenOfferGroupElement): Boolean {
|
||||||
return false
|
// return false
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
@@ -4,28 +4,27 @@ import android.os.Bundle
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.denizk0461.studip.adapter.CanteenOfferItemAdapter
|
import com.denizk0461.studip.adapter.CanteenOfferItemAdapter
|
||||||
import com.denizk0461.studip.adapter.StudIPEventPageAdapter
|
import com.denizk0461.studip.adapter.StudIPEventPageAdapter
|
||||||
import com.denizk0461.studip.databinding.RecyclerViewBinding
|
import com.denizk0461.studip.databinding.RecyclerViewBinding
|
||||||
|
import com.denizk0461.studip.model.CanteenOffer
|
||||||
import com.denizk0461.studip.model.CanteenOfferGroup
|
import com.denizk0461.studip.model.CanteenOfferGroup
|
||||||
|
import com.denizk0461.studip.model.CanteenOfferGroupElement
|
||||||
|
import com.denizk0461.studip.model.DietaryPreferences
|
||||||
|
import com.denizk0461.studip.sheet.AllergenSheet
|
||||||
|
import com.denizk0461.studip.viewmodel.CanteenPageViewModel
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fragment that is instantiated by [StudIPEventPageAdapter] to display individual days' pages and
|
* Fragment that is instantiated by [StudIPEventPageAdapter] to display individual days' pages and
|
||||||
* their events.
|
* their events.
|
||||||
*
|
*
|
||||||
* @param offers all offers for a given canteen, grouped by category (not filtered by day at
|
* @param currentDay day to display
|
||||||
* this point)
|
|
||||||
* @param onClickListener for managing click and long press events
|
|
||||||
* @param displayAllergens whether the user wants allergens to be marked
|
|
||||||
* @param currentDay current day
|
|
||||||
*/
|
*/
|
||||||
class CanteenPageFragment(
|
class CanteenPageFragment(
|
||||||
private var offers: List<CanteenOfferGroup>,
|
|
||||||
private val onClickListener: CanteenOfferItemAdapter.OnClickListener,
|
|
||||||
private val displayAllergens: Boolean,
|
|
||||||
private val currentDay: Int,
|
private val currentDay: Int,
|
||||||
) : AppFragment() {
|
) : AppFragment(), CanteenOfferItemAdapter.OnClickListener {
|
||||||
|
|
||||||
// Nullable view binding reference
|
// Nullable view binding reference
|
||||||
private var _binding: RecyclerViewBinding? = null
|
private var _binding: RecyclerViewBinding? = null
|
||||||
@@ -36,6 +35,13 @@ class CanteenPageFragment(
|
|||||||
*/
|
*/
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
|
// View model reference for providing access to the database
|
||||||
|
private val viewModel: CanteenPageViewModel by viewModels()
|
||||||
|
|
||||||
|
private lateinit var recyclerViewAdapter: CanteenOfferItemAdapter
|
||||||
|
|
||||||
|
private val offerList: MutableList<CanteenOffer> = mutableListOf()
|
||||||
|
|
||||||
// Instantiate the view binding
|
// Instantiate the view binding
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
_binding = RecyclerViewBinding.inflate(inflater, container, false)
|
_binding = RecyclerViewBinding.inflate(inflater, container, false)
|
||||||
@@ -45,6 +51,11 @@ class CanteenPageFragment(
|
|||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
recyclerViewAdapter = CanteenOfferItemAdapter(
|
||||||
|
this,
|
||||||
|
viewModel.preferenceAllergen,
|
||||||
|
)
|
||||||
|
|
||||||
binding.recyclerView.apply {
|
binding.recyclerView.apply {
|
||||||
// Set page to scroll vertically
|
// Set page to scroll vertically
|
||||||
layoutManager = LinearLayoutManager(
|
layoutManager = LinearLayoutManager(
|
||||||
@@ -56,16 +67,141 @@ class CanteenPageFragment(
|
|||||||
* by the newly created adapter (0 = Monday, 4 = Friday)
|
* by the newly created adapter (0 = Monday, 4 = Friday)
|
||||||
* TODO check if the filtered list is empty, and tell the user if it is
|
* TODO check if the filtered list is empty, and tell the user if it is
|
||||||
*/
|
*/
|
||||||
val o = offers.filter { it.dateId == currentDay }
|
// val o = offers.filter { it.dateId == currentDay }
|
||||||
|
|
||||||
adapter = CanteenOfferItemAdapter(
|
adapter = recyclerViewAdapter
|
||||||
o,
|
|
||||||
onClickListener,
|
|
||||||
displayAllergens,
|
|
||||||
)
|
|
||||||
|
|
||||||
// Animate creation of new page
|
// Animate creation of new page
|
||||||
scheduleLayoutAnimation()
|
scheduleLayoutAnimation()
|
||||||
|
|
||||||
|
viewModel.getOffersByDay(currentDay).observe(viewLifecycleOwner) { offers ->
|
||||||
|
offerList.clear()
|
||||||
|
offerList.addAll(offers)
|
||||||
|
recyclerViewAdapter.setNewData(offers.filterElements().groupElements().distinct())
|
||||||
|
// filterList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
viewModel.dietaryPreferencesUpdate.observe(viewLifecycleOwner) {
|
||||||
|
filterList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun filterList() {
|
||||||
|
recyclerViewAdapter.setNewData(offerList.filterElements().groupElements().distinct())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Groups [CanteenOffer] elements by their categories into [CanteenOfferGroup] elements and
|
||||||
|
* filters for the user's dietary preferences.
|
||||||
|
*
|
||||||
|
* @return the grouped and filtered elements
|
||||||
|
*/
|
||||||
|
private fun List<CanteenOffer>.groupElements(): List<CanteenOfferGroup> {
|
||||||
|
|
||||||
|
return map { (_, date, dateId, category, _, canteen, _, _, _, _, _) ->
|
||||||
|
// Create new group elements to group the already filtered elements by their categories
|
||||||
|
CanteenOfferGroup(
|
||||||
|
date,
|
||||||
|
dateId,
|
||||||
|
category,
|
||||||
|
canteen,
|
||||||
|
filter {
|
||||||
|
it.category == category && it.date == date && it.canteen == canteen
|
||||||
|
}.map {
|
||||||
|
// Map the individual items to their respective groups
|
||||||
|
CanteenOfferGroupElement(
|
||||||
|
it.title,
|
||||||
|
it.price,
|
||||||
|
it.dietaryPreferences,
|
||||||
|
it.allergens
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun List<CanteenOffer>.filterElements(): List<CanteenOffer> {
|
||||||
|
|
||||||
|
// Get dietary preference regex
|
||||||
|
val prefsRegex = getPrefRegex()
|
||||||
|
|
||||||
|
return if (prefsRegex.toString() == DietaryPreferences.TEMPLATE_EMPTY) {
|
||||||
|
// Show all elements and skip filtering if no preference is set
|
||||||
|
this
|
||||||
|
} else {
|
||||||
|
// Filter for dietary preferences
|
||||||
|
this.filter {
|
||||||
|
prefsRegex.matches(it.dietaryPreferences)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the user's dietary preferences and compiles them into a regular expression that can
|
||||||
|
* be used to filter out items that don't fulfil the preferences. Example:
|
||||||
|
* .......t..|........t.
|
||||||
|
* This will retrieve all items that marked as either vegetarian or vegan
|
||||||
|
* *
|
||||||
|
* @return regular expression object reflecting the user's preferences
|
||||||
|
*/
|
||||||
|
private fun getPrefRegex(): Regex {
|
||||||
|
// Retrieve the user's dietary preference as a string
|
||||||
|
val prefs = viewModel.getDietaryPrefs().deconstruct()
|
||||||
|
|
||||||
|
// Return the 'empty' template string if no preference has been set or found
|
||||||
|
return if (prefs == DietaryPreferences.TEMPLATE_EMPTY) {
|
||||||
|
Regex(DietaryPreferences.TEMPLATE_EMPTY)
|
||||||
|
} else {
|
||||||
|
// Create the variable to insert the regular expression into
|
||||||
|
var regexString = ""
|
||||||
|
|
||||||
|
// Create the object to store which preferences need to be met
|
||||||
|
val indices = mutableListOf<Int>()
|
||||||
|
|
||||||
|
// Find all preferences that need to be met
|
||||||
|
prefs.forEachIndexed { index, c ->
|
||||||
|
if (c == DietaryPreferences.C_TRUE) indices.add(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Needs to be set to correctly assemble the regular expression
|
||||||
|
var isFirst = true
|
||||||
|
|
||||||
|
// Iterate through every preference that has been set
|
||||||
|
indices.forEach { index ->
|
||||||
|
// Set an OR if more than one preference needs to be met
|
||||||
|
if (!isFirst) regexString += '|'
|
||||||
|
|
||||||
|
// Add the expression looking for the single preference to the entire string
|
||||||
|
regexString += DietaryPreferences.TEMPLATE_EMPTY.substring(0 until index) +
|
||||||
|
DietaryPreferences.C_TRUE +
|
||||||
|
DietaryPreferences.TEMPLATE_EMPTY.substring(index+1)
|
||||||
|
|
||||||
|
// Ensure that OR statements will be placed between the individual expressions
|
||||||
|
isFirst = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile the string to a regular expression object
|
||||||
|
Regex(regexString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executed when an item has been clicked.
|
||||||
|
*
|
||||||
|
* @param offer item that has been clicked
|
||||||
|
*/
|
||||||
|
override fun onClick(offer: CanteenOfferGroupElement, category: String) {
|
||||||
|
openBottomSheet(AllergenSheet(offer, category))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executed when an item has been long-pressed.
|
||||||
|
*
|
||||||
|
* @param offer item that has been long-pressed
|
||||||
|
* @return whether the long press was successful
|
||||||
|
*/
|
||||||
|
override fun onLongClick(offer: CanteenOfferGroupElement): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -137,6 +137,8 @@ enum class DietaryPreferences(val value: String) {
|
|||||||
*/
|
*/
|
||||||
const val C_FALSE: Char = '.'
|
const val C_FALSE: Char = '.'
|
||||||
|
|
||||||
|
const val TEMPLATE_EMPTY: String = ".........."
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a DietaryPrefObject from a regex string. String must be 10 characters long.
|
* Constructs a DietaryPrefObject from a regex string. String must be 10 characters long.
|
||||||
* A char equal to C_TRUE is treated as a true boolean value. Any other char is treated as a
|
* A char equal to C_TRUE is treated as a true boolean value. Any other char is treated as a
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.denizk0461.studip.viewmodel
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import com.denizk0461.studip.model.CanteenOffer
|
||||||
|
import com.denizk0461.studip.model.DietaryPreferences
|
||||||
|
import com.denizk0461.studip.model.SettingsPreferences
|
||||||
|
|
||||||
|
class CanteenPageViewModel(app: Application) : AppViewModel(app) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves all canteen offers that match given day.
|
||||||
|
*
|
||||||
|
* @return all canteen offers matching the given day exposed through a LiveData object
|
||||||
|
*/
|
||||||
|
fun getOffersByDay(day: Int): LiveData<List<CanteenOffer>> =
|
||||||
|
repo.getOffersByDay(day)
|
||||||
|
|
||||||
|
val dietaryPreferencesUpdate: LiveData<Int> = repo.dietaryPreferencesUpdate
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the user's dietary preferences.
|
||||||
|
*
|
||||||
|
* @return dietary preferences
|
||||||
|
*/
|
||||||
|
fun getDietaryPrefs(): DietaryPreferences.Object = repo.getDietaryPrefsAsObject()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This value determines whether the user wants to have allergens marked.
|
||||||
|
*/
|
||||||
|
val preferenceAllergen: Boolean
|
||||||
|
get() = repo.getBooleanPreference(SettingsPreferences.ALLERGEN, defaultValue = true)
|
||||||
|
}
|
||||||
@@ -22,12 +22,7 @@ class CanteenViewModel(app: Application) : AppViewModel(app) {
|
|||||||
*/
|
*/
|
||||||
val allOffers: LiveData<List<CanteenOffer>> = repo.allOffers
|
val allOffers: LiveData<List<CanteenOffer>> = repo.allOffers
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the user's dietary preferences.
|
|
||||||
*
|
|
||||||
* @return dietary preferences
|
|
||||||
*/
|
|
||||||
fun getDietaryPrefs(): DietaryPreferences.Object = repo.getDietaryPrefsAsObject()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves all canteen offer date objects.
|
* Retrieves all canteen offer date objects.
|
||||||
@@ -54,6 +49,17 @@ class CanteenViewModel(app: Application) : AppViewModel(app) {
|
|||||||
doAsync { parser.parse(canteen, onRefreshUpdate, onFinish) }
|
doAsync { parser.parse(canteen, onRefreshUpdate, onFinish) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun registerDietaryPreferencesUpdate() {
|
||||||
|
repo.dietaryPreferencesUpdate.postValue(repo.dietaryPreferencesUpdate.value?.plus(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the amount of dates represented in the offers stored locally. Can be observed.
|
||||||
|
*
|
||||||
|
* @return date count as LiveData
|
||||||
|
*/
|
||||||
|
fun getDateCount(): LiveData<Int> = repo.getDateCount()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates a given dietary preference to a new value.
|
* Updates a given dietary preference to a new value.
|
||||||
*
|
*
|
||||||
|
|||||||
Reference in New Issue
Block a user