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.ViewGroup
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.denizk0461.studip.R
|
||||
import com.denizk0461.studip.data.AppDiffUtilCallback
|
||||
import com.denizk0461.studip.databinding.ItemCanteenBinding
|
||||
import com.denizk0461.studip.databinding.ItemCanteenLineBinding
|
||||
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.
|
||||
*
|
||||
* @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 displayAllergens whether the user wants allergens to be marked
|
||||
*/
|
||||
class CanteenOfferItemAdapter(
|
||||
private val offers: List<CanteenOfferGroup>,
|
||||
private val onClickListener: OnClickListener,
|
||||
private val displayAllergens: Boolean,
|
||||
) : RecyclerView.Adapter<CanteenOfferItemAdapter.OfferViewHolder>() {
|
||||
|
||||
private val offers: MutableList<CanteenOfferGroup> = mutableListOf()
|
||||
|
||||
/**
|
||||
* 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 (indices.isEmpty()) indices.add(10)
|
||||
if (indices.isEmpty()) indices.add(DietaryPreferences.NONE.ordinal)
|
||||
|
||||
// Iterate through all icons that need to be displayed
|
||||
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 '*'
|
||||
* next to the title.
|
||||
|
||||
@@ -4,53 +4,29 @@ import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import com.denizk0461.studip.fragment.CanteenPageFragment
|
||||
import com.denizk0461.studip.model.CanteenOfferGroup
|
||||
|
||||
|
||||
/**
|
||||
* Custom ViewPager adapter for managing multiple pages of canteen offers.
|
||||
*
|
||||
* @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(
|
||||
fragmentActivity: FragmentActivity,
|
||||
private var offers: List<CanteenOfferGroup>,
|
||||
private var daysCovered: Int,
|
||||
private val onClickListener: CanteenOfferItemAdapter.OnClickListener,
|
||||
private val displayAllergens: Boolean,
|
||||
) : FragmentStateAdapter(fragmentActivity) {
|
||||
|
||||
private var count: Int = 0
|
||||
|
||||
// Returns the count of days covered
|
||||
override fun getItemCount(): Int = daysCovered
|
||||
override fun getItemCount(): Int = count
|
||||
|
||||
override fun createFragment(position: Int): Fragment =
|
||||
CanteenPageFragment(
|
||||
offers,
|
||||
onClickListener,
|
||||
displayAllergens,
|
||||
position,
|
||||
)
|
||||
|
||||
/**
|
||||
* Update the entire list of items
|
||||
*
|
||||
* @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
|
||||
fun setItemCount(newCount: Int) {
|
||||
count = newCount
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ class StudIPEventItemAdapter(
|
||||
/**
|
||||
* 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.
|
||||
|
||||
@@ -240,7 +240,7 @@ class StwParser(application: Application) {
|
||||
categoryId,
|
||||
title = "NO\$ITEMS",
|
||||
price = "",
|
||||
dietaryPreferences = "..........",
|
||||
dietaryPreferences = "tttttttttt",
|
||||
allergens = "",
|
||||
)
|
||||
)
|
||||
@@ -324,7 +324,6 @@ class StwParser(application: Application) {
|
||||
text = text.substring(0 until indexSupOpen) +
|
||||
text.substring((indexSupClose + 6) until text.length)
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the stripped and updated HTML string and join the allergen list to a cohesive
|
||||
* string without duplication.
|
||||
|
||||
@@ -97,6 +97,30 @@ interface AppDAO {
|
||||
)
|
||||
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.
|
||||
*
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.denizk0461.studip.db
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.denizk0461.studip.model.*
|
||||
|
||||
@@ -25,6 +26,11 @@ class AppRepository(app: Application) {
|
||||
// Blank dietary preference regular expression
|
||||
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 {
|
||||
/**
|
||||
* 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
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
|
||||
@@ -8,11 +8,9 @@ import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.denizk0461.studip.R
|
||||
import com.denizk0461.studip.adapter.CanteenOfferItemAdapter
|
||||
import com.denizk0461.studip.adapter.CanteenOfferPageAdapter
|
||||
import com.denizk0461.studip.databinding.FragmentCanteenBinding
|
||||
import com.denizk0461.studip.model.*
|
||||
import com.denizk0461.studip.sheet.AllergenSheet
|
||||
import com.denizk0461.studip.sheet.TextSheet
|
||||
import com.denizk0461.studip.viewmodel.CanteenViewModel
|
||||
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
|
||||
* Studierendenwerk Bremen.
|
||||
*/
|
||||
class CanteenFragment : AppFragment(), CanteenOfferItemAdapter.OnClickListener {
|
||||
class CanteenFragment : AppFragment() {
|
||||
|
||||
// Nullable view binding reference
|
||||
private var _binding: FragmentCanteenBinding? = null
|
||||
@@ -83,7 +81,7 @@ class CanteenFragment : AppFragment(), CanteenOfferItemAdapter.OnClickListener {
|
||||
binding.swipeRefreshLayout.isRefreshing = true
|
||||
|
||||
// Refresh the canteen menu for the newly selected canteen
|
||||
refresh()
|
||||
// refresh()
|
||||
true
|
||||
}
|
||||
inflate(R.menu.menu_canteens)
|
||||
@@ -148,40 +146,41 @@ class CanteenFragment : AppFragment(), CanteenOfferItemAdapter.OnClickListener {
|
||||
// Set up the view pager's adapter
|
||||
viewPagerAdapter = CanteenOfferPageAdapter(
|
||||
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
|
||||
binding.viewPager.adapter = viewPagerAdapter
|
||||
|
||||
// Set up LiveData observer to refresh the view on update
|
||||
viewModel.allOffers.observe(viewLifecycleOwner) { offers ->
|
||||
// Update the element list stored in this fragment
|
||||
elements = offers
|
||||
|
||||
// Group elements by category
|
||||
val groupedElements = offers.groupElements().distinct()
|
||||
|
||||
// Find all dates for which items are available
|
||||
val newDates = groupedElements.map { it.date }.distinct()
|
||||
|
||||
// Update the date count stored in this fragment
|
||||
dateSize = newDates.size
|
||||
|
||||
// Update the item list in the view pager's adapter
|
||||
viewPagerAdapter.setNewItems(groupedElements, dateSize)
|
||||
|
||||
// Set the text for the opening hours dialogue
|
||||
openingHours = viewModel.getCanteenOpeningHours()
|
||||
|
||||
binding.swipeRefreshLayout.isRefreshing = false
|
||||
|
||||
// Create and attach the tab layout for the ViewPager
|
||||
createTabLayoutMediator(newDates)
|
||||
}
|
||||
// viewModel.allOffers.observe(viewLifecycleOwner) { offers ->
|
||||
// // Update the element list stored in this fragment
|
||||
// elements = offers
|
||||
//
|
||||
// // Group elements by category
|
||||
// val groupedElements = offers.groupElements().distinct()
|
||||
//
|
||||
// // Find all dates for which items are available
|
||||
// val newDates = groupedElements.map { it.date }.distinct()
|
||||
//
|
||||
// // Update the date count stored in this fragment
|
||||
// dateSize = newDates.size
|
||||
//
|
||||
// // Update the item list in the view pager's adapter
|
||||
//// viewPagerAdapter.setNewItems(groupedElements, dateSize)
|
||||
//
|
||||
// // Set the text for the opening hours dialogue
|
||||
// openingHours = viewModel.getCanteenOpeningHours()
|
||||
//
|
||||
// binding.swipeRefreshLayout.isRefreshing = false
|
||||
//
|
||||
// // Create and attach the tab layout for the ViewPager
|
||||
// createTabLayoutMediator(newDates)
|
||||
// }
|
||||
|
||||
// Set up functions for when the user swipes to refresh the view
|
||||
binding.swipeRefreshLayout.setOnRefreshListener {
|
||||
@@ -223,7 +222,8 @@ class CanteenFragment : AppFragment(), CanteenOfferItemAdapter.OnClickListener {
|
||||
viewModel.setPreference(pref, newValue)
|
||||
|
||||
// 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 =
|
||||
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.
|
||||
*
|
||||
@@ -370,17 +279,17 @@ class CanteenFragment : AppFragment(), CanteenOfferItemAdapter.OnClickListener {
|
||||
*
|
||||
* @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
|
||||
}
|
||||
// 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
|
||||
// }
|
||||
}
|
||||
@@ -4,28 +4,27 @@ import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.denizk0461.studip.adapter.CanteenOfferItemAdapter
|
||||
import com.denizk0461.studip.adapter.StudIPEventPageAdapter
|
||||
import com.denizk0461.studip.databinding.RecyclerViewBinding
|
||||
import com.denizk0461.studip.model.CanteenOffer
|
||||
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
|
||||
* their events.
|
||||
*
|
||||
* @param offers all offers for a given canteen, grouped by category (not filtered by day at
|
||||
* 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
|
||||
* @param currentDay day to display
|
||||
*/
|
||||
class CanteenPageFragment(
|
||||
private var offers: List<CanteenOfferGroup>,
|
||||
private val onClickListener: CanteenOfferItemAdapter.OnClickListener,
|
||||
private val displayAllergens: Boolean,
|
||||
private val currentDay: Int,
|
||||
) : AppFragment() {
|
||||
) : AppFragment(), CanteenOfferItemAdapter.OnClickListener {
|
||||
|
||||
// Nullable view binding reference
|
||||
private var _binding: RecyclerViewBinding? = null
|
||||
@@ -36,6 +35,13 @@ class CanteenPageFragment(
|
||||
*/
|
||||
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
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
_binding = RecyclerViewBinding.inflate(inflater, container, false)
|
||||
@@ -45,6 +51,11 @@ class CanteenPageFragment(
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
recyclerViewAdapter = CanteenOfferItemAdapter(
|
||||
this,
|
||||
viewModel.preferenceAllergen,
|
||||
)
|
||||
|
||||
binding.recyclerView.apply {
|
||||
// Set page to scroll vertically
|
||||
layoutManager = LinearLayoutManager(
|
||||
@@ -56,16 +67,141 @@ class CanteenPageFragment(
|
||||
* by the newly created adapter (0 = Monday, 4 = Friday)
|
||||
* 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(
|
||||
o,
|
||||
onClickListener,
|
||||
displayAllergens,
|
||||
)
|
||||
adapter = recyclerViewAdapter
|
||||
|
||||
// Animate creation of new page
|
||||
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 TEMPLATE_EMPTY: String = ".........."
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
@@ -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
|
||||
|
||||
/**
|
||||
* Retrieve the user's dietary preferences.
|
||||
*
|
||||
* @return dietary preferences
|
||||
*/
|
||||
fun getDietaryPrefs(): DietaryPreferences.Object = repo.getDietaryPrefsAsObject()
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves all canteen offer date objects.
|
||||
@@ -54,6 +49,17 @@ class CanteenViewModel(app: Application) : AppViewModel(app) {
|
||||
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.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user