Made some improvements to the dietary preference handling, but CanteenOfferPageAdapter.kt still needs some work for the filtering to work properly; it should only filter out elements if they meet NO preferences!

This commit is contained in:
denizk0461
2023-04-18 15:39:49 +02:00
parent c385da4287
commit 4d9ad39578
12 changed files with 252 additions and 113 deletions
@@ -6,8 +6,9 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.denizk0461.studip.databinding.ItemScrollablePageBinding
import com.denizk0461.studip.model.CanteenOffer
import com.denizk0461.studip.model.DietaryPrefObject
class CanteenOfferPageAdapter(private var offers: List<CanteenOffer>, private var daysCovered: Int) : RecyclerView.Adapter<CanteenOfferPageAdapter.CanteenOfferPageViewHolder>() {
class CanteenOfferPageAdapter(private var offers: List<CanteenOffer>, private var daysCovered: Int, private var prefs: DietaryPrefObject) : RecyclerView.Adapter<CanteenOfferPageAdapter.CanteenOfferPageViewHolder>() {
class CanteenOfferPageViewHolder(val binding: ItemScrollablePageBinding) : RecyclerView.ViewHolder(binding.root)
@@ -28,7 +29,8 @@ class CanteenOfferPageAdapter(private var offers: List<CanteenOffer>, private va
layoutManager = LinearLayoutManager(holder.binding.root.context, LinearLayoutManager.VERTICAL, false)
val filtered = offers.filter { it.dateId == position }
// TODO second filter needs to be worked on; it shouldn't filter out something if two options are selected and only one is met
val filtered = offers.filter { it.dateId == position }.filter { it.dietaryPreferences == prefs.deconstruct() }
adapter = CanteenOfferItemAdapter(filtered) // TODO check if empty
scheduleLayoutAnimation()
}
@@ -39,4 +41,9 @@ class CanteenOfferPageAdapter(private var offers: List<CanteenOffer>, private va
offers = items
notifyDataSetChanged()
}
fun refreshView(prefs: DietaryPrefObject) {
this.prefs = prefs
notifyDataSetChanged()
}
}
@@ -76,12 +76,7 @@ class StwParser {
val prefs =
element.getElementsByClass("field field-name-field-food-types")[0]
repo.insert(
OfferItem(
itemId,
categoryId,
title = tableRows[1].getFilteredText(),
price = tableRows.getTextOrEmpty(2),
val prefString = DietaryPrefObject(
isFair = prefs.isDietaryPreferenceMet(PREFERENCE_FAIR),
isFish = prefs.isDietaryPreferenceMet(PREFERENCE_FISH),
isPoultry = prefs.isDietaryPreferenceMet(PREFERENCE_POULTRY),
@@ -92,6 +87,17 @@ class StwParser {
isVegan = prefs.isDietaryPreferenceMet(PREFERENCE_VEGAN),
isVegetarian = prefs.isDietaryPreferenceMet(PREFERENCE_VEGETARIAN),
isGame = prefs.isDietaryPreferenceMet(PREFERENCE_GAME),
).deconstruct()
// Log.d("eek!4", "prefString = $prefString")
repo.insert(
OfferItem(
itemId,
categoryId,
title = tableRows[1].getFilteredText(),
price = tableRows.getTextOrEmpty(2),
prefString,
)
)
itemId += 1
@@ -46,14 +46,21 @@ interface EventDAO {
"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 isFair = :isFair AND isFish = :isFish AND isPoultry = :isPoultry " +
"AND isLamb = :isLamb AND isVital = :isVital AND isBeef = :isBeef " +
"AND isPork = :isPork AND isVegan = :isVegan AND isVegetarian = :isVegetarian " +
"AND isGame = :isGame "
"WHERE dietary_preferences = :prefs "
// "WHERE isFair = :isFair " +
// "OR isFish = :isFish " +
// "OR isPoultry = :isPoultry " +
// "OR isLamb = :isLamb " +
// "OR isVital = :isVital " +
// "OR isBeef = :isBeef " +
// "OR isPork = :isPork " +
// "OR isVegan = :isVegan " +
// "OR isVegetarian = :isVegetarian " +
// "OR isGame = :isGame "
)
fun getOffersByPreference(
isFair: Boolean, isFish: Boolean, isPoultry: Boolean, isLamb: Boolean, isVital: Boolean,
isBeef: Boolean, isPork: Boolean, isVegan: Boolean, isVegetarian: Boolean, isGame: Boolean
fun getOffersByPreference(prefs: String
// isFair: Boolean, isFish: Boolean, isPoultry: Boolean, isLamb: Boolean, isVital: Boolean,
// isBeef: Boolean, isPork: Boolean, isVegan: Boolean, isVegetarian: Boolean, isGame: Boolean
): LiveData<List<CanteenOffer>>
@get:Query("SELECT id FROM offer_date")
@@ -14,7 +14,7 @@ import com.denizk0461.studip.model.*
OfferCategory::class,
OfferItem::class
],
version = 8
version = 10,
)
abstract class EventDatabase : RoomDatabase() {
@@ -1,6 +1,7 @@
package com.denizk0461.studip.db
import android.app.Application
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.preference.PreferenceManager
import com.denizk0461.studip.model.*
@@ -12,6 +13,8 @@ class EventRepository(private val app: Application) {
val allEvents: LiveData<List<StudIPEvent>> = dao.allEvents
private val dietaryPrefString = "prefs_obj"
fun insertEvent(event: StudIPEvent) { dao.insertEvent(event) }
fun insertEvents(events: List<StudIPEvent>) { dao.insertEvents(events) }
fun nukeEvents() { dao.nukeEvents() }
@@ -19,63 +22,100 @@ class EventRepository(private val app: Application) {
val allOffers: LiveData<List<CanteenOffer>> = dao.allOffers
// is this better than List.filter?
fun getOffersByPreference(prefs: DietaryPrefObject = getDietaryPrefs()): LiveData<List<CanteenOffer>> {
return if (isPreferenceSet()) {
fun getOffersByPreference(prefs: String = getDietaryPrefs()): LiveData<List<CanteenOffer>> {
return if (prefs != "ffffffffff") {//isPreferenceSet()) {
dao.getOffersByPreference(
isFair = prefs.isFair,
isFish = prefs.isFish,
isPoultry = prefs.isPoultry,
isLamb = prefs.isLamb,
isVital = prefs.isVital,
isBeef = prefs.isBeef,
isPork = prefs.isPork,
isVegan = prefs.isVegan,
isVegetarian = prefs.isVegetarian,
isGame = prefs.isGame,
prefs
// isFair = prefs.isFair,
// isFish = prefs.isFish,
// isPoultry = prefs.isPoultry,
// isLamb = prefs.isLamb,
// isVital = prefs.isVital,
// isBeef = prefs.isBeef,
// isPork = prefs.isPork,
// isVegan = prefs.isVegan,
// isVegetarian = prefs.isVegetarian,
// isGame = prefs.isGame,
)
} else {
dao.allOffers
}
// return dao.allOffers
}
private fun getDietaryPrefs(): DietaryPrefObject {
return DietaryPrefObject(
isFair = prefs.getBoolean(DietaryPreferences.FAIR.value, false),
isFish = prefs.getBoolean(DietaryPreferences.FISH.value, false),
isPoultry = prefs.getBoolean(DietaryPreferences.POULTRY.value, false),
isLamb = prefs.getBoolean(DietaryPreferences.LAMB.value, false),
isVital = prefs.getBoolean(DietaryPreferences.VITAL.value, false),
isBeef = prefs.getBoolean(DietaryPreferences.BEEF.value, false),
isPork = prefs.getBoolean(DietaryPreferences.PORK.value, false),
isVegan = prefs.getBoolean(DietaryPreferences.VEGAN.value, false),
isVegetarian = prefs.getBoolean(DietaryPreferences.VEGETARIAN.value, false),
isGame = prefs.getBoolean(DietaryPreferences.GAME.value, false),
)
fun getDietaryPrefs(): String {
return prefs.getString(dietaryPrefString, "ffffffffff") ?: "ffffffffff"
// return DietaryPrefObject(
// isFair = prefs.getBoolean(DietaryPreferences.FAIR.value, false),
// isFish = prefs.getBoolean(DietaryPreferences.FISH.value, false),
// isPoultry = prefs.getBoolean(DietaryPreferences.POULTRY.value, false),
// isLamb = prefs.getBoolean(DietaryPreferences.LAMB.value, false),
// isVital = prefs.getBoolean(DietaryPreferences.VITAL.value, false),
// isBeef = prefs.getBoolean(DietaryPreferences.BEEF.value, false),
// isPork = prefs.getBoolean(DietaryPreferences.PORK.value, false),
// isVegan = prefs.getBoolean(DietaryPreferences.VEGAN.value, false),
// isVegetarian = prefs.getBoolean(DietaryPreferences.VEGETARIAN.value, false),
// isGame = prefs.getBoolean(DietaryPreferences.GAME.value, false),
// )
}
private fun isPreferenceSet(): Boolean {
getDietaryPrefs().also { p ->
if (p.isFair) return true
if (p.isFish) return true
if (p.isPoultry) return true
if (p.isLamb) return true
if (p.isVital) return true
if (p.isBeef) return true
if (p.isPork) return true
if (p.isVegan) return true
if (p.isVegetarian) return true
if (p.isGame) return true
}
return false
}
fun getDietaryPrefsAsObj(): DietaryPrefObject = DietaryPrefObject.construct(getDietaryPrefs())
// private fun isPreferenceSet(): Boolean {
// getDietaryPrefs().also { p ->
// if (p.isFair) return true
// if (p.isFish) return true
// if (p.isPoultry) return true
// if (p.isLamb) return true
// if (p.isVital) return true
// if (p.isBeef) return true
// if (p.isPork) return true
// if (p.isVegan) return true
// if (p.isVegetarian) return true
// if (p.isGame) return true
// }
// return false
// }
fun setPreference(pref: DietaryPreferences, newValue: Boolean) {
prefs.edit().putBoolean(pref.value, newValue).apply()
// prefs.edit().putBoolean(pref.value, newValue).apply()
val oldString = getDietaryPrefs().toMutableList()
when (pref) {
DietaryPreferences.FAIR -> oldString[0] = newValue.toChar()
DietaryPreferences.FISH -> oldString[1] = newValue.toChar()
DietaryPreferences.POULTRY -> oldString[2] = newValue.toChar()
DietaryPreferences.LAMB -> oldString[3] = newValue.toChar()
DietaryPreferences.VITAL -> oldString[4] = newValue.toChar()
DietaryPreferences.BEEF -> oldString[5] = newValue.toChar()
DietaryPreferences.PORK -> oldString[6] = newValue.toChar()
DietaryPreferences.VEGAN -> oldString[7] = newValue.toChar()
DietaryPreferences.VEGETARIAN -> oldString[8] = newValue.toChar()
DietaryPreferences.GAME -> oldString[9] = newValue.toChar()
}
val newString = oldString.joinToString("")
Log.d("eek!4", newString)
prefs.edit().putString(dietaryPrefString, newString).apply()
}
fun getPreference(pref: DietaryPreferences): Boolean =
prefs.getBoolean(pref.value, false)
private fun Boolean.toChar() = if (this) 't' else 'f'
fun getPreference(pref: DietaryPreferences): Boolean {
val prefString = getDietaryPrefs().toList()
return when (pref) {
DietaryPreferences.FAIR -> prefString[0] == 't'
DietaryPreferences.FISH -> prefString[1] == 't'
DietaryPreferences.POULTRY -> prefString[2] == 't'
DietaryPreferences.LAMB -> prefString[3] == 't'
DietaryPreferences.VITAL -> prefString[4] == 't'
DietaryPreferences.BEEF -> prefString[5] == 't'
DietaryPreferences.PORK -> prefString[6] == 't'
DietaryPreferences.VEGAN -> prefString[7] == 't'
DietaryPreferences.VEGETARIAN -> prefString[8] == 't'
DietaryPreferences.GAME -> prefString[9] == 't'
}
}
// prefs.getBoolean(pref.value, false)
// fun insertOffers(offers: List<CanteenOffer>) { dao.insertOffers(offers) }
fun nukeOffers() {
@@ -1,14 +1,18 @@
package com.denizk0461.studip.fragment
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.children
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.denizk0461.studip.adapter.CanteenOfferPageAdapter
import com.denizk0461.studip.databinding.FragmentCanteenBinding
import com.denizk0461.studip.model.CanteenOffer
import com.denizk0461.studip.model.DietaryPreferences
import com.denizk0461.studip.viewmodel.CanteenViewModel
import com.google.android.material.chip.Chip
@@ -24,6 +28,8 @@ class CanteenFragment : Fragment() {
private var dates = listOf<String>()
private lateinit var liveData: LiveData<List<CanteenOffer>>
private lateinit var viewPagerAdapter: CanteenOfferPageAdapter
private val viewModel: CanteenViewModel by viewModels()
@@ -35,21 +41,7 @@ class CanteenFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
/* Since this removes the view before immediately adding it back, this method causes a
* flicker. Note for the future: implement a more efficient / better-looking method.
*
* TODO order the chips alphabetically
*/
for (chip in binding.chipsPreference.children) {
val nChip = chip as Chip
nChip.setOnCheckedChangeListener { buttonView, _ ->
val index = binding.chipsPreference.indexOfChild(buttonView)
binding.chipsPreference.removeView(buttonView)
binding.chipsPreference.addView(buttonView, index)
}
}
val chipMap = mapOf<Chip, DietaryPreferences>(
val chipMap = mapOf(
binding.chipPrefFair to DietaryPreferences.FAIR,
binding.chipPrefFish to DietaryPreferences.FISH,
binding.chipPrefPoultry to DietaryPreferences.POULTRY,
@@ -62,28 +54,57 @@ class CanteenFragment : Fragment() {
binding.chipPrefGame to DietaryPreferences.GAME,
)
chipMap.forEach { chip, pref ->
chipMap.forEach { (chip, pref) ->
chip.isChecked = getPreference(pref)
chip.setOnCheckedChangeListener { _, newValue ->
chip.setOnCheckedChangeListener { buttonView, newValue ->
setPreference(pref, newValue)
Log.d("eek!4", "object as : ${viewModel.getDietaryPrefs().deconstruct()}")
/* Since this removes the view before immediately adding it back, this method causes a
* flicker. Note for the future: implement a more efficient / better-looking method.
*
* TODO order the chips alphabetically
*/
val index = binding.chipsPreference.indexOfChild(buttonView)
binding.chipsPreference.removeView(buttonView)
binding.chipsPreference.addView(buttonView, index)
}
}
binding.chipPrefFair
binding.chipPrefFair.setOnCheckedChangeListener { _, newValue ->
setPreference(DietaryPreferences.FAIR, newValue)
}
// binding.chipPrefFair
// binding.chipPrefFair.setOnCheckedChangeListener { _, newValue ->
// setPreference(DietaryPreferences.FAIR, newValue)
// }
viewPagerAdapter = CanteenOfferPageAdapter(listOf(), 0)
viewPagerAdapter = CanteenOfferPageAdapter(listOf(), 0, viewModel.getDietaryPrefs())
binding.viewPager.adapter = viewPagerAdapter
createTabLayoutMediator()
viewModel.allOffers.observe(viewLifecycleOwner) { offers ->
liveData = viewModel.getOffersByPreference()
liveData.observe(viewLifecycleOwner) { offers ->
dates = offers.map { it.date }.distinct()
createTabLayoutMediator()
// val newOffers = mutableListOf<CanteenOffer>()
// val prefs = viewModel.getDietaryPrefs()
// if (prefs.isFair) { newOffers.addAll(offers.filter { it.isFair }) }
// if (prefs.isFish) { newOffers.addAll(offers.filter { it.isFish }) }
// if (prefs.isPoultry) { newOffers.addAll(offers.filter { it.isPoultry }) }
// if (prefs.isLamb) { newOffers.addAll(offers.filter { it.isLamb }) }
// if (prefs.isVital) { newOffers.addAll(offers.filter { it.isVital }) }
// if (prefs.isBeef) { newOffers.addAll(offers.filter { it.isBeef }) }
// if (prefs.isPork) { newOffers.addAll(offers.filter { it.isPork }) }
// if (prefs.isVegetarian) { newOffers.addAll(offers.filter { it.isVegetarian }) }
// if (prefs.isVegan) { newOffers.addAll(offers.filter { it.isVegan }) }
// if (prefs.isGame) { newOffers.addAll(offers.filter { it.isGame }) }
// newOffers.sortBy { it ->
// Log.d("eek!4", "title: ${it.title} - id: ${it.itemId}")
// it.itemId
// }
viewPagerAdapter.setNewItems(offers, dates.size)
// TODO this must be changed. if an exception is raised, then this will not fire
@@ -92,7 +113,7 @@ class CanteenFragment : Fragment() {
binding.swipeRefreshLayout.setOnRefreshListener {
viewModel.fetchOffers(onRefreshUpdate = { status ->
// TODO refresh updates
}, onFinish = {
// binding.swipeRefreshLayout.isRefreshing = false
})
@@ -114,9 +135,17 @@ class CanteenFragment : Fragment() {
private fun setPreference(pref: DietaryPreferences, newValue: Boolean) {
viewModel.setPreference(pref, newValue)
viewPagerAdapter.refreshView(viewModel.getDietaryPrefs())
// Log.d("eek!4", "this")
// liveData.forceRefresh()
// viewModel.forceRefresh()
// notifyDataSetChanged?
}
private fun getPreference(pref: DietaryPreferences): Boolean =
viewModel.getPreference(pref)
private fun MutableLiveData<List<CanteenOffer>>.forceRefresh() {
this.value = this.value
}
}
@@ -1,9 +1,11 @@
package com.denizk0461.studip.model
import androidx.room.ColumnInfo
//@Entity(tableName = "offers")
data class CanteenOffer(
// @PrimaryKey val id: Int,
val id: Int,
val itemId: Int,
val date: String,
val dateId: Int,
val category: String,
@@ -12,14 +14,5 @@ data class CanteenOffer(
val canteenId: Int,
val title: String,
val price: String, // price for students
val isFair: Boolean, // artgerechte Tierhaltung
val isFish: Boolean, // Fisch
val isPoultry: Boolean, // Geflügel
val isLamb: Boolean, // Lamm
val isVital: Boolean, // mensaVital
val isBeef: Boolean, // Rindfleisch
val isPork: Boolean, // Schweinefleisch
val isVegan: Boolean, // plant-based (vegan)
val isVegetarian: Boolean, // vegetarisch
val isGame: Boolean, // Wild
@ColumnInfo(name = "dietary_preferences") val dietaryPreferences: String,
)
@@ -11,4 +11,38 @@ data class DietaryPrefObject(
val isVegan: Boolean,
val isVegetarian: Boolean,
val isGame: Boolean,
)
) {
companion object {
/* assume that the values must be parsed as follows:
* "xxxxxxxxxx"
* t = true, f = false
*/
fun construct(values: String) = DietaryPrefObject(
isFair = values[0] == 't',
isFish = values[1] == 't',
isPoultry = values[2] == 't',
isLamb = values[3] == 't',
isVital = values[4] == 't',
isBeef = values[5] == 't',
isPork = values[6] == 't',
isVegan = values[7] == 't',
isVegetarian = values[8] == 't',
isGame = values[9] == 't',
)
}
fun deconstruct() = String(
charArrayOf(
if (this.isFair) 't' else 'f',
if (this.isFish) 't' else 'f',
if (this.isPoultry) 't' else 'f',
if (this.isLamb) 't' else 'f',
if (this.isVital) 't' else 'f',
if (this.isBeef) 't' else 'f',
if (this.isPork) 't' else 'f',
if (this.isVegan) 't' else 'f',
if (this.isVegetarian) 't' else 'f',
if (this.isGame) 't' else 'f',
)
)
}
@@ -1,5 +1,6 @@
package com.denizk0461.studip.model
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
@@ -16,18 +17,19 @@ import androidx.room.PrimaryKey
]
)
data class OfferItem(
@PrimaryKey val id: Int,
@PrimaryKey val itemId: Int,
val categoryId: Int,
val title: String,
val price: String,
val isFair: Boolean,
val isFish: Boolean,
val isPoultry: Boolean,
val isLamb: Boolean,
val isVital: Boolean,
val isBeef: Boolean,
val isPork: Boolean,
val isVegan: Boolean,
val isVegetarian: Boolean,
val isGame: Boolean,
@ColumnInfo(name = "dietary_preferences") val dietaryPreferences: String,
// val isFair: Boolean,
// val isFish: Boolean,
// val isPoultry: Boolean,
// val isLamb: Boolean,
// val isVital: Boolean,
// val isBeef: Boolean,
// val isPork: Boolean,
// val isVegan: Boolean,
// val isVegetarian: Boolean,
// val isGame: Boolean,
)
@@ -2,18 +2,32 @@ package com.denizk0461.studip.viewmodel
import android.app.Application
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.denizk0461.studip.data.StwParser
import com.denizk0461.studip.model.CanteenOffer
import com.denizk0461.studip.model.DietaryPrefObject
import com.denizk0461.studip.model.DietaryPreferences
class CanteenViewModel(app: Application) : TemplateViewModel(app) {
private val parser = StwParser()
// bad pattern i know
private val mutableOffers = MutableLiveData<List<CanteenOffer>>()
val allOffers: LiveData<List<CanteenOffer>> = repo.allOffers
fun getOffersByPreference(): LiveData<List<CanteenOffer>> =
repo.getOffersByPreference()
fun getOffersByPreference(): LiveData<List<CanteenOffer>> {
val of = repo.getOffersByPreference()
mutableOffers.postValue(of.value)
return of
}
fun getDietaryPrefs(): DietaryPrefObject = repo.getDietaryPrefsAsObj()
fun forceRefresh() {
mutableOffers.value = mutableOffers.value
}
// fun insertOffers(offers: List<CanteenOffer>) { doAsync { repo.insertOffers(offers) }}
fun nukeOffers() { doAsync { repo.nukeOffers() }}
+5
View File
@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>
+3 -1
View File
@@ -61,7 +61,9 @@
android:id="@+id/chip_pref_fish"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pref_fish"/>
android:text="@string/pref_fish"
app:chipIcon="@drawable/cross"
app:checkedIcon="@drawable/flatware"/>
<com.google.android.material.chip.Chip
style="@style/Widget.Material3.Chip.Filter"