Archived
Fully documented all Kotlin files and added TODOs to functions that need to be updated in some way
This commit is contained in:
@@ -7,29 +7,44 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.LiveData
|
||||
import com.denizk0461.studip.adapter.CanteenOfferPageAdapter
|
||||
import com.denizk0461.studip.databinding.FragmentCanteenBinding
|
||||
import com.denizk0461.studip.model.*
|
||||
import com.denizk0461.studip.viewmodel.CanteenViewModel
|
||||
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 : Fragment() {
|
||||
|
||||
// Nullable view binding reference
|
||||
private var _binding: FragmentCanteenBinding? = null
|
||||
|
||||
// This property is only valid between onCreateView and
|
||||
// onDestroyView.
|
||||
// BUG: if the fragment is changed before the refresh is finished, the app crashes with a NPE
|
||||
/*
|
||||
* Non-null reference to the view binding. This property is only valid between onCreateView and
|
||||
* onDestroyView.
|
||||
* BUG if the fragment is changed before the refresh is finished, the app crashes with a NPE
|
||||
*/
|
||||
private val binding get() = _binding!!
|
||||
|
||||
// Adapter for the view pager displaying all offers
|
||||
private lateinit var viewPagerAdapter: CanteenOfferPageAdapter
|
||||
|
||||
// View model reference for providing access to the database
|
||||
private val viewModel: CanteenViewModel by viewModels()
|
||||
|
||||
|
||||
// Elements in the canteen plan
|
||||
private var elements: List<CanteenOffer> = listOf()
|
||||
private var dateSize = 0
|
||||
|
||||
// Count of days displayed in the canteen plan
|
||||
private var dateSize: Int = 0
|
||||
|
||||
// Used if no preference has been set or none can be found
|
||||
private val emptyPreferenceRegex: String = ".........."
|
||||
|
||||
// Instantiate the view binding
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
_binding = FragmentCanteenBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
@@ -38,6 +53,7 @@ class CanteenFragment : Fragment() {
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
// Assign a preference value to every button to filter for dietary preferences
|
||||
val chipMap = mapOf(
|
||||
binding.chipPrefFair to DietaryPreferences.FAIR,
|
||||
binding.chipPrefFish to DietaryPreferences.FISH,
|
||||
@@ -51,15 +67,21 @@ class CanteenFragment : Fragment() {
|
||||
binding.chipPrefGame to DietaryPreferences.GAME,
|
||||
)
|
||||
|
||||
// Set up every chip
|
||||
chipMap.forEach { (chip, pref) ->
|
||||
// Set chip to be checked if user has set this preference before
|
||||
chip.isChecked = getPreference(pref)
|
||||
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.
|
||||
*
|
||||
// Set a listener for when the user clicks the chip
|
||||
chip.setOnCheckedChangeListener { buttonView, newValue ->
|
||||
// Save the preference change to persistent storage
|
||||
setPreference(pref, newValue)
|
||||
|
||||
/*
|
||||
* Force an animation on click. This is a subpar method from StackOverflow. Since
|
||||
* it removes the view before immediately adding it back, this method causes a
|
||||
* flicker.
|
||||
* TODO implement a more efficient / better-looking method
|
||||
* TODO order the chips alphabetically
|
||||
*/
|
||||
val index = binding.chipsPreference.indexOfChild(buttonView)
|
||||
@@ -68,29 +90,42 @@ class CanteenFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
viewPagerAdapter = CanteenOfferPageAdapter(listOf(), 0, getPrefRegex())
|
||||
// Set up the view pager's adapter
|
||||
viewPagerAdapter = CanteenOfferPageAdapter(listOf(), 0)
|
||||
|
||||
// Assign the adapter to the view pager
|
||||
binding.viewPager.adapter = viewPagerAdapter
|
||||
|
||||
// Create and attach the tab mediator for the view pager
|
||||
createTabLayoutMediator()
|
||||
|
||||
// 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()
|
||||
|
||||
val newDates = groupedElements.map { it.date }.distinct()//.map { item -> OfferDate(item.) }
|
||||
// val newDates = viewModel.getDates()
|
||||
// Find all dates stored in the database
|
||||
val newDates = groupedElements.map { it.date }.distinct() // TODO viewModel.getDates()
|
||||
|
||||
// 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)
|
||||
// createTabLayoutMediator(newDates)
|
||||
|
||||
// TODO this must be changed. if an exception is raised, then this will not fire
|
||||
/*
|
||||
* Tell the swipe refresh layout to stop refreshing.
|
||||
* TODO if an exception is raised, this will not be fired. This must be changed
|
||||
*/
|
||||
binding.swipeRefreshLayout.isRefreshing = false
|
||||
}
|
||||
|
||||
// Set up functions for when the user swipes to refresh the view
|
||||
binding.swipeRefreshLayout.setOnRefreshListener {
|
||||
// Retrieve new offers from the website(s)
|
||||
viewModel.fetchOffers(onRefreshUpdate = { status ->
|
||||
// TODO refresh updates
|
||||
}, onFinish = {
|
||||
@@ -100,7 +135,11 @@ class CanteenFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun createTabLayoutMediator(){//List<OfferDate>) {
|
||||
/**
|
||||
* Create and attach the object mediating the tabs for the view pager.
|
||||
*/
|
||||
private fun createTabLayoutMediator() {
|
||||
// Fetch all dates from the database
|
||||
val dates = viewModel.getDates()
|
||||
if (dates.isNotEmpty()) {
|
||||
Log.d("eek!7", "dates: ${dates}, viewpager pages: ${binding.viewPager.adapter?.itemCount}")
|
||||
@@ -111,57 +150,106 @@ class CanteenFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
// Invalidate the view binding
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a given preference and refreshes the view.
|
||||
*
|
||||
* @param pref preference to be updated
|
||||
* @param newValue value to set the preference to
|
||||
*/
|
||||
private fun setPreference(pref: DietaryPreferences, newValue: Boolean) {
|
||||
// Update the preference
|
||||
viewModel.setPreference(pref, newValue)
|
||||
|
||||
// Update the view
|
||||
viewPagerAdapter.setNewItems(elements.groupElements().distinct(), dateSize)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single user-specified dietary preference.
|
||||
*
|
||||
* @param pref the dietary preference that should be retrieved
|
||||
* @return whether the preference needs to be met
|
||||
*/
|
||||
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 {
|
||||
val prefs = viewModel.getDietaryPrefs().deconstruct()//.replace('f', '.')
|
||||
// Retrieve the user's dietary preference as a string
|
||||
val prefs = viewModel.getDietaryPrefs().deconstruct()
|
||||
|
||||
val template = ".........."
|
||||
|
||||
return if (prefs == "..........") {
|
||||
Regex(template)
|
||||
// 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 == 't') indices.add(index)
|
||||
if (c == DietaryPrefObject.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 += '|'
|
||||
regexString += template.substring(0 until index) + 't' + template.substring(index+1)
|
||||
|
||||
// Add the expression looking for the single preference to the entire string
|
||||
regexString += emptyPreferenceRegex.substring(0 until index) +
|
||||
DietaryPrefObject.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 filteredForPreferences = if (prefsRegex.toString() == "ffffffffff") {
|
||||
// show all elements and skip filtering
|
||||
val filteredForPreferences = 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)
|
||||
}
|
||||
}
|
||||
|
||||
val b = filteredForPreferences.map { offer ->
|
||||
return filteredForPreferences.map { offer ->
|
||||
// Create new group elements to group the already filtered elements by their categories
|
||||
CanteenOfferGroup(
|
||||
offer.date,
|
||||
offer.dateId,
|
||||
@@ -170,6 +258,7 @@ class CanteenFragment : Fragment() {
|
||||
filteredForPreferences.filter {
|
||||
it.category == offer.category && it.date == offer.date && it.canteen == offer.canteen
|
||||
}.map {
|
||||
// Map the individual items to their respective groups
|
||||
CanteenOfferGroupElement(
|
||||
it.title,
|
||||
it.price,
|
||||
@@ -178,6 +267,5 @@ class CanteenFragment : Fragment() {
|
||||
}
|
||||
)
|
||||
}
|
||||
return b
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user