From c385da428732ac31339cc7f98adca7cb2a22d0a4 Mon Sep 17 00:00:00 2001 From: denizk0461 Date: Tue, 18 Apr 2023 10:25:45 +0200 Subject: [PATCH] Implemented chips for selecting dietary preferences --- app/build.gradle | 2 + .../com/denizk0461/studip/data/StwParser.kt | 7 +- .../java/com/denizk0461/studip/db/EventDAO.kt | 18 +++ .../denizk0461/studip/db/EventRepository.kt | 63 +++++++++- .../studip/fragment/CanteenFragment.kt | 56 ++++++++- .../studip/model/DietaryPrefObject.kt | 14 +++ .../studip/model/DietaryPreferences.kt | 14 +++ .../studip/viewmodel/CanteenViewModel.kt | 9 +- app/src/main/res/layout/fragment_canteen.xml | 116 +++++++++++++++--- app/src/main/res/values-de/strings.xml | 12 ++ app/src/main/res/values/strings.xml | 12 ++ 11 files changed, 299 insertions(+), 24 deletions(-) create mode 100644 app/src/main/java/com/denizk0461/studip/model/DietaryPrefObject.kt create mode 100644 app/src/main/java/com/denizk0461/studip/model/DietaryPreferences.kt diff --git a/app/build.gradle b/app/build.gradle index 7f58cf2..702aecf 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -53,5 +53,7 @@ dependencies { implementation "androidx.room:room-runtime: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' } \ No newline at end of file diff --git a/app/src/main/java/com/denizk0461/studip/data/StwParser.kt b/app/src/main/java/com/denizk0461/studip/data/StwParser.kt index c40a3c6..700eb2f 100644 --- a/app/src/main/java/com/denizk0461/studip/data/StwParser.kt +++ b/app/src/main/java/com/denizk0461/studip/data/StwParser.kt @@ -15,7 +15,7 @@ class StwParser { private var categoryId = 0 private var itemId = 0 - fun parse(onFinish: () -> Unit) { + fun parse(onRefreshUpdate: (status: Int) -> Unit, onFinish: () -> Unit) { dateId = 0 canteenId = 0 categoryId = 0 @@ -36,6 +36,7 @@ class StwParser { // STEP: fetch every canteen links.forEach { link -> + // TODO implement onRefresh(Int) parseFromPage(link, Dependencies.repo) } } @@ -47,10 +48,8 @@ class StwParser { dateId = 0 val doc = Jsoup.connect(url).get() - val t = doc.getElementsByClass("pane-title")[1].text() - repo.insert(OfferCanteen(canteenId, t)) // TODO canteen name - Log.d("eek!3", t) + repo.insert(OfferCanteen(canteenId, doc.getElementsByClass("pane-title")[1].text())) // STEP: get each day doc.getElementsByClass("food-plan").forEach { dayPlan -> diff --git a/app/src/main/java/com/denizk0461/studip/db/EventDAO.kt b/app/src/main/java/com/denizk0461/studip/db/EventDAO.kt index 25efae5..59e0bbe 100644 --- a/app/src/main/java/com/denizk0461/studip/db/EventDAO.kt +++ b/app/src/main/java/com/denizk0461/studip/db/EventDAO.kt @@ -41,6 +41,24 @@ interface EventDAO { ) val allOffers: LiveData> + @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> + + @get:Query("SELECT id FROM offer_date") + val updateObserver: LiveData> + @Query("DELETE FROM offer_date") fun nukeOfferDates() diff --git a/app/src/main/java/com/denizk0461/studip/db/EventRepository.kt b/app/src/main/java/com/denizk0461/studip/db/EventRepository.kt index 3ecbb9f..07255d4 100644 --- a/app/src/main/java/com/denizk0461/studip/db/EventRepository.kt +++ b/app/src/main/java/com/denizk0461/studip/db/EventRepository.kt @@ -2,11 +2,13 @@ package com.denizk0461.studip.db import android.app.Application import androidx.lifecycle.LiveData +import androidx.preference.PreferenceManager 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 prefs = PreferenceManager.getDefaultSharedPreferences(app) val allEvents: LiveData> = dao.allEvents @@ -16,6 +18,65 @@ class EventRepository(app: Application) { val allOffers: LiveData> = dao.allOffers + // is this better than List.filter? + fun getOffersByPreference(prefs: DietaryPrefObject = getDietaryPrefs()): LiveData> { + 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) { dao.insertOffers(offers) } fun nukeOffers() { dao.nukeOfferItems() diff --git a/app/src/main/java/com/denizk0461/studip/fragment/CanteenFragment.kt b/app/src/main/java/com/denizk0461/studip/fragment/CanteenFragment.kt index f4a2f58..0c510d7 100644 --- a/app/src/main/java/com/denizk0461/studip/fragment/CanteenFragment.kt +++ b/app/src/main/java/com/denizk0461/studip/fragment/CanteenFragment.kt @@ -4,11 +4,14 @@ import android.os.Bundle 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 com.denizk0461.studip.adapter.CanteenOfferPageAdapter import com.denizk0461.studip.databinding.FragmentCanteenBinding +import com.denizk0461.studip.model.DietaryPreferences import com.denizk0461.studip.viewmodel.CanteenViewModel +import com.google.android.material.chip.Chip import com.google.android.material.tabs.TabLayoutMediator class CanteenFragment : Fragment() { @@ -32,6 +35,45 @@ 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( + 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) binding.viewPager.adapter = viewPagerAdapter @@ -49,9 +91,11 @@ class CanteenFragment : Fragment() { } binding.swipeRefreshLayout.setOnRefreshListener { - viewModel.fetchOffers { + viewModel.fetchOffers(onRefreshUpdate = { status -> + + }, onFinish = { // binding.swipeRefreshLayout.isRefreshing = false - } + }) } } @@ -67,4 +111,12 @@ class CanteenFragment : Fragment() { super.onDestroyView() _binding = null } + + private fun setPreference(pref: DietaryPreferences, newValue: Boolean) { + viewModel.setPreference(pref, newValue) + // notifyDataSetChanged? + } + + private fun getPreference(pref: DietaryPreferences): Boolean = + viewModel.getPreference(pref) } \ No newline at end of file diff --git a/app/src/main/java/com/denizk0461/studip/model/DietaryPrefObject.kt b/app/src/main/java/com/denizk0461/studip/model/DietaryPrefObject.kt new file mode 100644 index 0000000..7f04fb2 --- /dev/null +++ b/app/src/main/java/com/denizk0461/studip/model/DietaryPrefObject.kt @@ -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, +) \ No newline at end of file diff --git a/app/src/main/java/com/denizk0461/studip/model/DietaryPreferences.kt b/app/src/main/java/com/denizk0461/studip/model/DietaryPreferences.kt new file mode 100644 index 0000000..a48fb7b --- /dev/null +++ b/app/src/main/java/com/denizk0461/studip/model/DietaryPreferences.kt @@ -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"), +} \ No newline at end of file diff --git a/app/src/main/java/com/denizk0461/studip/viewmodel/CanteenViewModel.kt b/app/src/main/java/com/denizk0461/studip/viewmodel/CanteenViewModel.kt index 38487fd..25758b0 100644 --- a/app/src/main/java/com/denizk0461/studip/viewmodel/CanteenViewModel.kt +++ b/app/src/main/java/com/denizk0461/studip/viewmodel/CanteenViewModel.kt @@ -4,6 +4,7 @@ import android.app.Application import androidx.lifecycle.LiveData import com.denizk0461.studip.data.StwParser import com.denizk0461.studip.model.CanteenOffer +import com.denizk0461.studip.model.DietaryPreferences class CanteenViewModel(app: Application) : TemplateViewModel(app) { @@ -11,8 +12,14 @@ class CanteenViewModel(app: Application) : TemplateViewModel(app) { val allOffers: LiveData> = repo.allOffers + fun getOffersByPreference(): LiveData> = + repo.getOffersByPreference() + // fun insertOffers(offers: List) { doAsync { repo.insertOffers(offers) }} 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) } \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_canteen.xml b/app/src/main/res/layout/fragment_canteen.xml index f7f6ca8..fa13de0 100644 --- a/app/src/main/res/layout/fragment_canteen.xml +++ b/app/src/main/res/layout/fragment_canteen.xml @@ -6,37 +6,121 @@ android:background="?attr/colorBackground" android:orientation="vertical"> + + + android:layout_height="0dp" + android:layout_weight="1"> - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 2be5335..488c561 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -22,6 +22,18 @@ Cafeteria Bremerhaven Interimsmensa HfK + + Artgerecht + Fisch + Huhn + Lamm + mensaVital + Rind + Schwein + Vegan + Vegetarisch + Wild + Stundenplan Mensen Einstellungen diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3430ced..16a9760 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -22,6 +22,18 @@ Cafeteria Bremerhaven Interimsmensa HfK + + Fair + Fish + Poultry + Lamb + mensaVital + Beef + Pork + Vegan + Vegetarian + Game + Schedule Canteens Settings