Implemented chips for selecting dietary preferences

This commit is contained in:
denizk0461
2023-04-18 10:25:45 +02:00
parent 56cdc4c5ee
commit c385da4287
11 changed files with 299 additions and 24 deletions
+2
View File
@@ -53,5 +53,7 @@ dependencies {
implementation "androidx.room:room-runtime:2.5.1" implementation "androidx.room:room-runtime:2.5.1"
kapt "androidx.room:room-compiler:2.5.1" kapt "androidx.room:room-compiler:2.5.1"
implementation 'androidx.preference:preference-ktx:1.2.0'
implementation 'org.jsoup:jsoup:1.15.4' implementation 'org.jsoup:jsoup:1.15.4'
} }
@@ -15,7 +15,7 @@ class StwParser {
private var categoryId = 0 private var categoryId = 0
private var itemId = 0 private var itemId = 0
fun parse(onFinish: () -> Unit) { fun parse(onRefreshUpdate: (status: Int) -> Unit, onFinish: () -> Unit) {
dateId = 0 dateId = 0
canteenId = 0 canteenId = 0
categoryId = 0 categoryId = 0
@@ -36,6 +36,7 @@ class StwParser {
// STEP: fetch every canteen // STEP: fetch every canteen
links.forEach { link -> links.forEach { link ->
// TODO implement onRefresh(Int)
parseFromPage(link, Dependencies.repo) parseFromPage(link, Dependencies.repo)
} }
} }
@@ -47,10 +48,8 @@ class StwParser {
dateId = 0 dateId = 0
val doc = Jsoup.connect(url).get() val doc = Jsoup.connect(url).get()
val t = doc.getElementsByClass("pane-title")[1].text()
repo.insert(OfferCanteen(canteenId, t)) // TODO canteen name repo.insert(OfferCanteen(canteenId, doc.getElementsByClass("pane-title")[1].text()))
Log.d("eek!3", t)
// STEP: get each day // STEP: get each day
doc.getElementsByClass("food-plan").forEach { dayPlan -> doc.getElementsByClass("food-plan").forEach { dayPlan ->
@@ -41,6 +41,24 @@ interface EventDAO {
) )
val allOffers: LiveData<List<CanteenOffer>> val allOffers: LiveData<List<CanteenOffer>>
@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 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 "
)
fun getOffersByPreference(
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")
val updateObserver: LiveData<List<Int>>
@Query("DELETE FROM offer_date") @Query("DELETE FROM offer_date")
fun nukeOfferDates() fun nukeOfferDates()
@@ -2,11 +2,13 @@ package com.denizk0461.studip.db
import android.app.Application import android.app.Application
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.preference.PreferenceManager
import com.denizk0461.studip.model.* import com.denizk0461.studip.model.*
class EventRepository(app: Application) { class EventRepository(private val app: Application) {
private val dao: EventDAO = EventDatabase.getInstance(app.applicationContext).dao() private val dao: EventDAO = EventDatabase.getInstance(app.applicationContext).dao()
private val prefs = PreferenceManager.getDefaultSharedPreferences(app)
val allEvents: LiveData<List<StudIPEvent>> = dao.allEvents val allEvents: LiveData<List<StudIPEvent>> = dao.allEvents
@@ -16,6 +18,65 @@ class EventRepository(app: Application) {
val allOffers: LiveData<List<CanteenOffer>> = dao.allOffers val allOffers: LiveData<List<CanteenOffer>> = dao.allOffers
// is this better than List.filter?
fun getOffersByPreference(prefs: DietaryPrefObject = getDietaryPrefs()): LiveData<List<CanteenOffer>> {
return if (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,
)
} else {
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),
)
}
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()
}
fun getPreference(pref: DietaryPreferences): Boolean =
prefs.getBoolean(pref.value, false)
// fun insertOffers(offers: List<CanteenOffer>) { dao.insertOffers(offers) } // fun insertOffers(offers: List<CanteenOffer>) { dao.insertOffers(offers) }
fun nukeOffers() { fun nukeOffers() {
dao.nukeOfferItems() dao.nukeOfferItems()
@@ -4,11 +4,14 @@ 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.core.view.children
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
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.DietaryPreferences
import com.denizk0461.studip.viewmodel.CanteenViewModel import com.denizk0461.studip.viewmodel.CanteenViewModel
import com.google.android.material.chip.Chip
import com.google.android.material.tabs.TabLayoutMediator import com.google.android.material.tabs.TabLayoutMediator
class CanteenFragment : Fragment() { class CanteenFragment : Fragment() {
@@ -32,6 +35,45 @@ class CanteenFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) 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>(
binding.chipPrefFair to DietaryPreferences.FAIR,
binding.chipPrefFish to DietaryPreferences.FISH,
binding.chipPrefPoultry to DietaryPreferences.POULTRY,
binding.chipPrefLamb to DietaryPreferences.LAMB,
binding.chipPrefVital to DietaryPreferences.VITAL,
binding.chipPrefBeef to DietaryPreferences.BEEF,
binding.chipPrefPork to DietaryPreferences.PORK,
binding.chipPrefVegetarian to DietaryPreferences.VEGETARIAN,
binding.chipPrefVegan to DietaryPreferences.VEGAN,
binding.chipPrefGame to DietaryPreferences.GAME,
)
chipMap.forEach { chip, pref ->
chip.isChecked = getPreference(pref)
chip.setOnCheckedChangeListener { _, newValue ->
setPreference(pref, newValue)
}
}
binding.chipPrefFair
binding.chipPrefFair.setOnCheckedChangeListener { _, newValue ->
setPreference(DietaryPreferences.FAIR, newValue)
}
viewPagerAdapter = CanteenOfferPageAdapter(listOf(), 0) viewPagerAdapter = CanteenOfferPageAdapter(listOf(), 0)
binding.viewPager.adapter = viewPagerAdapter binding.viewPager.adapter = viewPagerAdapter
@@ -49,9 +91,11 @@ class CanteenFragment : Fragment() {
} }
binding.swipeRefreshLayout.setOnRefreshListener { binding.swipeRefreshLayout.setOnRefreshListener {
viewModel.fetchOffers { viewModel.fetchOffers(onRefreshUpdate = { status ->
}, onFinish = {
// binding.swipeRefreshLayout.isRefreshing = false // binding.swipeRefreshLayout.isRefreshing = false
} })
} }
} }
@@ -67,4 +111,12 @@ class CanteenFragment : Fragment() {
super.onDestroyView() super.onDestroyView()
_binding = null _binding = null
} }
private fun setPreference(pref: DietaryPreferences, newValue: Boolean) {
viewModel.setPreference(pref, newValue)
// notifyDataSetChanged?
}
private fun getPreference(pref: DietaryPreferences): Boolean =
viewModel.getPreference(pref)
} }
@@ -0,0 +1,14 @@
package com.denizk0461.studip.model
data class DietaryPrefObject(
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,
)
@@ -0,0 +1,14 @@
package com.denizk0461.studip.model
enum class DietaryPreferences(val value: String) {
FAIR("isFair"),
FISH("isFish"),
POULTRY("isPoultry"),
LAMB("isLamb"),
VITAL("isVital"),
BEEF("isBeef"),
PORK("isPork"),
VEGAN("isVegan"),
VEGETARIAN("isVegetarian"),
GAME("isGame"),
}
@@ -4,6 +4,7 @@ import android.app.Application
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import com.denizk0461.studip.data.StwParser import com.denizk0461.studip.data.StwParser
import com.denizk0461.studip.model.CanteenOffer import com.denizk0461.studip.model.CanteenOffer
import com.denizk0461.studip.model.DietaryPreferences
class CanteenViewModel(app: Application) : TemplateViewModel(app) { class CanteenViewModel(app: Application) : TemplateViewModel(app) {
@@ -11,8 +12,14 @@ class CanteenViewModel(app: Application) : TemplateViewModel(app) {
val allOffers: LiveData<List<CanteenOffer>> = repo.allOffers val allOffers: LiveData<List<CanteenOffer>> = repo.allOffers
fun getOffersByPreference(): LiveData<List<CanteenOffer>> =
repo.getOffersByPreference()
// fun insertOffers(offers: List<CanteenOffer>) { doAsync { repo.insertOffers(offers) }} // fun insertOffers(offers: List<CanteenOffer>) { doAsync { repo.insertOffers(offers) }}
fun nukeOffers() { doAsync { repo.nukeOffers() }} fun nukeOffers() { doAsync { repo.nukeOffers() }}
fun fetchOffers(onFinish: () -> Unit) { doAsync { parser.parse(onFinish) }} fun fetchOffers(onRefreshUpdate: (status: Int) -> Unit, onFinish: () -> Unit) { doAsync { parser.parse(onRefreshUpdate, onFinish) }}
fun setPreference(pref: DietaryPreferences, newValue: Boolean) { repo.setPreference(pref, newValue) }
fun getPreference(pref: DietaryPreferences): Boolean = repo.getPreference(pref)
} }
+102 -18
View File
@@ -6,37 +6,121 @@
android:background="?attr/colorBackground" android:background="?attr/colorBackground"
android:orientation="vertical"> android:orientation="vertical">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.tabs.TabLayout <com.google.android.material.tabs.TabLayout
android:id="@+id/day_tab_layout" android:id="@+id/day_tab_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:tabMode="scrollable" app:tabMode="scrollable"
android:background="@android:color/transparent" android:background="@android:color/transparent"/>
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
app:layout_constraintEnd_toEndOf="parent"/> android:id="@+id/swipe_refresh_layout"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.viewpager2.widget.ViewPager2 <androidx.viewpager2.widget.ViewPager2
android:id="@+id/view_pager" android:id="@+id/view_pager"
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="match_parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/day_tab_layout" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/> app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.appcompat.widget.LinearLayoutCompat>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none">
<com.google.android.material.chip.ChipGroup
android:id="@+id/chips_preference"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:backgroundTint="@android:color/transparent"
android:animateLayoutChanges="true">
<com.google.android.material.chip.Chip
style="@style/Widget.Material3.Chip.Filter"
android:id="@+id/chip_pref_fair"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pref_fair"/>
<com.google.android.material.chip.Chip
style="@style/Widget.Material3.Chip.Filter"
android:id="@+id/chip_pref_fish"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pref_fish"/>
<com.google.android.material.chip.Chip
style="@style/Widget.Material3.Chip.Filter"
android:id="@+id/chip_pref_poultry"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pref_poultry"/>
<com.google.android.material.chip.Chip
style="@style/Widget.Material3.Chip.Filter"
android:id="@+id/chip_pref_lamb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pref_lamb"/>
<com.google.android.material.chip.Chip
style="@style/Widget.Material3.Chip.Filter"
android:id="@+id/chip_pref_vital"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pref_vital"/>
<com.google.android.material.chip.Chip
style="@style/Widget.Material3.Chip.Filter"
android:id="@+id/chip_pref_beef"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pref_beef"/>
<com.google.android.material.chip.Chip
style="@style/Widget.Material3.Chip.Filter"
android:id="@+id/chip_pref_pork"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pref_pork"/>
<com.google.android.material.chip.Chip
style="@style/Widget.Material3.Chip.Filter"
android:id="@+id/chip_pref_vegetarian"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pref_vegetarian"/>
<com.google.android.material.chip.Chip
style="@style/Widget.Material3.Chip.Filter"
android:id="@+id/chip_pref_vegan"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pref_vegan"/>
<com.google.android.material.chip.Chip
style="@style/Widget.Material3.Chip.Filter"
android:id="@+id/chip_pref_game"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pref_game"/>
</com.google.android.material.chip.ChipGroup>
</HorizontalScrollView>
</androidx.appcompat.widget.LinearLayoutCompat> </androidx.appcompat.widget.LinearLayoutCompat>
+12
View File
@@ -22,6 +22,18 @@
<string name="cafeteria_bhv">Cafeteria Bremerhaven</string> <string name="cafeteria_bhv">Cafeteria Bremerhaven</string>
<string name="mensa_hfk">Interimsmensa HfK</string> <string name="mensa_hfk">Interimsmensa HfK</string>
<!-- dietary preferences -->
<string name="pref_fair">Artgerecht</string>
<string name="pref_fish">Fisch</string>
<string name="pref_poultry">Huhn</string>
<string name="pref_lamb">Lamm</string>
<string name="pref_vital">mensaVital</string>
<string name="pref_beef">Rind</string>
<string name="pref_pork">Schwein</string>
<string name="pref_vegan">Vegan</string>
<string name="pref_vegetarian">Vegetarisch</string>
<string name="pref_game">Wild</string>
<string name="nav_schedule">Stundenplan</string> <string name="nav_schedule">Stundenplan</string>
<string name="nav_food">Mensen</string> <string name="nav_food">Mensen</string>
<string name="nav_settings">Einstellungen</string> <string name="nav_settings">Einstellungen</string>
+12
View File
@@ -22,6 +22,18 @@
<string name="cafeteria_bhv">Cafeteria Bremerhaven</string> <string name="cafeteria_bhv">Cafeteria Bremerhaven</string>
<string name="mensa_hfk">Interimsmensa HfK</string> <string name="mensa_hfk">Interimsmensa HfK</string>
<!-- dietary preferences -->
<string name="pref_fair">Fair</string> <!-- is this really the best name? what about 'animal welfare'? -->
<string name="pref_fish">Fish</string>
<string name="pref_poultry">Poultry</string>
<string name="pref_lamb">Lamb</string>
<string name="pref_vital">mensaVital</string>
<string name="pref_beef">Beef</string>
<string name="pref_pork">Pork</string>
<string name="pref_vegan">Vegan</string>
<string name="pref_vegetarian">Vegetarian</string>
<string name="pref_game">Game</string>
<string name="nav_schedule">Schedule</string> <string name="nav_schedule">Schedule</string>
<string name="nav_food">Canteens</string> <string name="nav_food">Canteens</string>
<string name="nav_settings">Settings</string> <string name="nav_settings">Settings</string>