Archived
TabLayoutMediator has been re-instantiated, hopefully in a form that is less crash-prone. Crashes will also be handled now in StwParser.kt
This commit is contained in:
@@ -8,10 +8,9 @@ import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.denizk0461.studip.R
|
||||
import com.denizk0461.studip.data.getThemedColor
|
||||
import com.denizk0461.studip.data.showErrorSnackBar
|
||||
import com.denizk0461.studip.databinding.ActivityFetcherBinding
|
||||
import com.denizk0461.studip.viewmodel.FetcherViewModel
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import java.io.IOException
|
||||
import java.net.URLDecoder
|
||||
|
||||
@@ -90,18 +89,8 @@ class FetcherActivity : Activity() {
|
||||
// finish()
|
||||
// }
|
||||
} catch (e: IOException) {
|
||||
|
||||
// Let the user know that an error occurred
|
||||
Snackbar
|
||||
.make(
|
||||
binding.rootView,
|
||||
getString(R.string.fetch_error_snack),
|
||||
Snackbar.LENGTH_SHORT
|
||||
)
|
||||
// Set colours to signify an error
|
||||
.setBackgroundTint(theme.getThemedColor(R.attr.colorErrorContainer))
|
||||
.setTextColor(theme.getThemedColor(R.attr.colorOnErrorContainer))
|
||||
.show()
|
||||
theme.showErrorSnackBar(binding.rootView, getString(R.string.fetch_error_snack))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,10 @@ import android.content.res.Resources
|
||||
import android.util.TypedValue
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import com.denizk0461.studip.R
|
||||
import com.denizk0461.studip.exception.AcademicQuarterNotApplicableException
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlin.jvm.Throws
|
||||
|
||||
/**
|
||||
@@ -42,6 +45,15 @@ fun showToast(context: Context, text: String) {
|
||||
Toast.makeText(context, text, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
fun Resources.Theme.showErrorSnackBar(view: CoordinatorLayout, text: String) {
|
||||
Snackbar
|
||||
.make(view, text, Snackbar.LENGTH_SHORT)
|
||||
// Set colours to signify an error
|
||||
.setBackgroundTint(getThemedColor(R.attr.colorErrorContainer))
|
||||
.setTextColor(getThemedColor(R.attr.colorOnErrorContainer))
|
||||
.show()
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a conversion method between a timestamp ending in a full hour, and one with an academic
|
||||
* quarter applied.
|
||||
|
||||
@@ -3,9 +3,12 @@ package com.denizk0461.studip.data
|
||||
import android.app.Application
|
||||
import com.denizk0461.studip.db.AppRepository
|
||||
import com.denizk0461.studip.model.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Element
|
||||
import org.jsoup.select.Elements
|
||||
import kotlin.jvm.Throws
|
||||
|
||||
/**
|
||||
* Parser class used for fetching and collecting canteen offers from the website of the
|
||||
@@ -36,11 +39,14 @@ class StwParser(application: Application) {
|
||||
/**
|
||||
* Parses through a list of canteen plans and saves them to persistent storage.
|
||||
*
|
||||
* @param onRefreshUpdate action call for when an update on the status of the fetch is
|
||||
* available
|
||||
* @param onFinish action call for when the fetch has finished
|
||||
* @param onError action call for when an error has occurred
|
||||
*/
|
||||
fun parse(canteen: Int, onRefreshUpdate: (status: Int) -> Unit, onFinish: () -> Unit) {
|
||||
suspend fun parse(
|
||||
canteen: Int,
|
||||
onFinish: () -> Unit,
|
||||
onError: () -> Unit
|
||||
) {
|
||||
/*
|
||||
* Reset primary key values. This needs to be done in case this method is called multiple
|
||||
* times within the app's lifespan
|
||||
@@ -65,30 +71,47 @@ class StwParser(application: Application) {
|
||||
else -> urlUniMensa
|
||||
}
|
||||
|
||||
// Delete all previous entries and start afresh
|
||||
repo.nukeOffers()
|
||||
/*
|
||||
* Handle the fetch operation in an encompassing try-catch to prevent the app from crashing.
|
||||
* User will be notified if an error occurs.
|
||||
*/
|
||||
try {
|
||||
|
||||
// Fetch offers from the chosen canteen
|
||||
val (dates, canteens, categories, items) = parseFromPage(link)
|
||||
|
||||
// Delete all previous entries and start afresh
|
||||
repo.nukeOffers()
|
||||
|
||||
// Save everything all at once into the database
|
||||
with (repo) {
|
||||
with(repo) {
|
||||
insertDates(dates)
|
||||
insertCanteens(canteens)
|
||||
insertCategories(categories)
|
||||
insertItems(items)
|
||||
}
|
||||
// TODO implement onRefresh(Int)
|
||||
|
||||
// Action call once all fetching activities have finished
|
||||
// Call action to let the user know the fetch has finished
|
||||
onFinish()
|
||||
|
||||
} catch (e: RuntimeException) {
|
||||
/*
|
||||
* Call action to let the user know an error occurred. Do this on the main thread to
|
||||
* access UI.
|
||||
*/
|
||||
withContext(Dispatchers.Main) {
|
||||
onError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch and parse the plan of a specific canteen.
|
||||
*
|
||||
* @param url link to the canteen to be scraped. Must be a subpage of stw-bremen.de
|
||||
* @throws RuntimeException if something goes wrong during the fetch
|
||||
*/
|
||||
@Throws(RuntimeException::class)
|
||||
private fun parseFromPage(url: String): StwResults {
|
||||
|
||||
val dates = mutableListOf<OfferDate>()
|
||||
@@ -189,25 +212,34 @@ class StwParser(application: Application) {
|
||||
// Retrieve the parent element holding the item's title and price
|
||||
val tableRows = element.getElementsByTag("td")
|
||||
|
||||
// Retrieve the dietary preferences the item meets
|
||||
val prefs = element
|
||||
/*
|
||||
* Retrieve the dietary preferences the item meets and parse them into a
|
||||
* string that can be inserted into the database.
|
||||
*/
|
||||
val prefs = try {
|
||||
with(element
|
||||
.getElementsByClass("field field-name-field-food-types")[0]
|
||||
) {
|
||||
DietaryPreferences.Object(
|
||||
isFair = isDietaryPreferenceMet(imageLinkPrefFair),
|
||||
isFish = isDietaryPreferenceMet(imageLinkPrefFish),
|
||||
isPoultry = isDietaryPreferenceMet(imageLinkPrefPoultry),
|
||||
isLamb = isDietaryPreferenceMet(imageLinkPrefLamb),
|
||||
isVital = isDietaryPreferenceMet(imageLinkPrefVital),
|
||||
isBeef = isDietaryPreferenceMet(imageLinkPrefBeef),
|
||||
isPork = isDietaryPreferenceMet(imageLinkPrefPork),
|
||||
isVegan = isDietaryPreferenceMet(imageLinkPrefVegan),
|
||||
isVegetarian = isDietaryPreferenceMet(imageLinkPrefVegetarian),
|
||||
isGame = isDietaryPreferenceMet(imageLinkPrefGame),
|
||||
)
|
||||
}
|
||||
} catch (e: IndexOutOfBoundsException) {
|
||||
DietaryPreferences.NONE_MET
|
||||
}
|
||||
|
||||
// Parse the preferences into a string for the database
|
||||
val prefString = DietaryPreferences.Object(
|
||||
isFair = prefs.isDietaryPreferenceMet(imageLinkPrefFair),
|
||||
isFish = prefs.isDietaryPreferenceMet(imageLinkPrefFish),
|
||||
isPoultry = prefs.isDietaryPreferenceMet(imageLinkPrefPoultry),
|
||||
isLamb = prefs.isDietaryPreferenceMet(imageLinkPrefLamb),
|
||||
isVital = prefs.isDietaryPreferenceMet(imageLinkPrefVital),
|
||||
isBeef = prefs.isDietaryPreferenceMet(imageLinkPrefBeef),
|
||||
isPork = prefs.isDietaryPreferenceMet(imageLinkPrefPork),
|
||||
isVegan = prefs.isDietaryPreferenceMet(imageLinkPrefVegan),
|
||||
isVegetarian = prefs.isDietaryPreferenceMet(imageLinkPrefVegetarian),
|
||||
isGame = prefs.isDietaryPreferenceMet(imageLinkPrefGame),
|
||||
).deconstruct()
|
||||
|
||||
val filteredText = tableRows[1].getFilteredText()
|
||||
val filteredText = getOrElse(orElse = Pair("", "")) {
|
||||
tableRows[1].getFilteredText()
|
||||
}
|
||||
|
||||
// Save the item to its list
|
||||
items.add(
|
||||
@@ -216,7 +248,7 @@ class StwParser(application: Application) {
|
||||
categoryId,
|
||||
title = filteredText.first,
|
||||
price = tableRows.getTextOrEmpty(2),
|
||||
dietaryPreferences = prefString,
|
||||
dietaryPreferences = prefs.deconstruct(),
|
||||
allergens = filteredText.second,
|
||||
)
|
||||
)
|
||||
@@ -300,7 +332,6 @@ class StwParser(application: Application) {
|
||||
/*
|
||||
* The Studierendenwerk's website lists allergens, but they are invisible, rendering them
|
||||
* pointless to the website user. As of now, this information is discarded in the app.
|
||||
* TODO implement allergen functionality
|
||||
*/
|
||||
while (text.contains("<sup>")) {
|
||||
|
||||
@@ -367,6 +398,12 @@ class StwParser(application: Application) {
|
||||
else -> "00" // shouldn't occur
|
||||
}
|
||||
|
||||
private fun <T> getOrElse(orElse: T, action: () -> T): T = try {
|
||||
action()
|
||||
} catch (e: Exception) {
|
||||
orElse
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a text and adds <br> tags with line breaks.
|
||||
*
|
||||
@@ -407,7 +444,7 @@ class StwParser(application: Application) {
|
||||
private val urlGW2 = "https://www.stw-bremen.de/de/cafeteria/gw2"
|
||||
// private val urlGraz = "" // No link since there are only snacks on offer that are not listed online
|
||||
private val urlHSBNeustadt = "https://www.stw-bremen.de/de/mensa/neustadtswall"
|
||||
private val urlHSBWerder = "https://www.stw-bremen.de/de/mensa/werderstra%C3%9Fe"
|
||||
private val urlHSBWerder = "https://www.stw-bremen.de/de/mensa/werderstraße"
|
||||
private val urlHSBAirport = "https://www.stw-bremen.de/de/mensa/airport"
|
||||
private val urlHfK = "https://www.stw-bremen.de/de/mensa/interimsmensa-hfk"
|
||||
private val urlMensaBHV = "https://www.stw-bremen.de/de/mensa/bremerhaven"
|
||||
|
||||
@@ -67,20 +67,6 @@ interface AppDAO {
|
||||
|
||||
/* --- canteen offers --- */
|
||||
|
||||
/**
|
||||
* Retrieves all canteen offers. 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 exposed through a LiveData object
|
||||
*/
|
||||
@get: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"
|
||||
)
|
||||
val allOffers: LiveData<List<CanteenOffer>>
|
||||
|
||||
/**
|
||||
* Retrieves all canteen offers that match given dietary preferences. Objects will be joined
|
||||
* through their primary/foreign keys from instances of OfferDate.kt, OfferCanteen.kt,
|
||||
@@ -113,21 +99,13 @@ interface AppDAO {
|
||||
)
|
||||
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.
|
||||
*
|
||||
* @return a list of instances of canteen offer dates
|
||||
*/
|
||||
@Query("SELECT * FROM offer_date ORDER BY id")
|
||||
fun getDates(): List<OfferDate>
|
||||
fun getDates(): LiveData<List<OfferDate>>
|
||||
|
||||
/**
|
||||
* Deletes all dates from the database.
|
||||
|
||||
@@ -78,13 +78,6 @@ class AppRepository(app: Application) {
|
||||
*/
|
||||
fun getCanteenOpeningHours(): String = dao.getCanteenOpeningHours()
|
||||
|
||||
/**
|
||||
* Retrieves all canteen offers.
|
||||
*
|
||||
* @return all canteen offers exposed through a LiveData object
|
||||
*/
|
||||
val allOffers: LiveData<List<CanteenOffer>> = dao.allOffers
|
||||
|
||||
/**
|
||||
* Retrieves all canteen offers that match given day.
|
||||
*
|
||||
@@ -92,13 +85,6 @@ class AppRepository(app: Application) {
|
||||
*/
|
||||
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.
|
||||
*
|
||||
@@ -122,7 +108,7 @@ class AppRepository(app: Application) {
|
||||
*
|
||||
* @return a list of instances of canteen offer dates
|
||||
*/
|
||||
fun getDates(): List<OfferDate> = dao.getDates()
|
||||
fun getDates(): LiveData<List<OfferDate>> = dao.getDates()
|
||||
|
||||
/**
|
||||
* Retrieves the regex string used to determine the user's dietary preferences, or, if no
|
||||
|
||||
@@ -9,6 +9,7 @@ import androidx.fragment.app.FragmentActivity
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.denizk0461.studip.R
|
||||
import com.denizk0461.studip.adapter.CanteenOfferPageAdapter
|
||||
import com.denizk0461.studip.data.showErrorSnackBar
|
||||
import com.denizk0461.studip.databinding.FragmentCanteenBinding
|
||||
import com.denizk0461.studip.model.*
|
||||
import com.denizk0461.studip.sheet.TextSheet
|
||||
@@ -37,17 +38,10 @@ class CanteenFragment : AppFragment() {
|
||||
// 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()
|
||||
|
||||
// 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 = ".........."
|
||||
|
||||
private var openingHours = ""
|
||||
|
||||
private var dates: List<String> = listOf()
|
||||
|
||||
// Instantiate the view binding
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
_binding = FragmentCanteenBinding.inflate(inflater, container, false)
|
||||
@@ -78,10 +72,10 @@ class CanteenFragment : AppFragment() {
|
||||
binding.buttonCanteenPicker.text = getCurrentlySelectedCanteenName()
|
||||
|
||||
// Display to the user that the canteen plan will be refreshed
|
||||
binding.swipeRefreshLayout.isRefreshing = true
|
||||
// binding.swipeRefreshLayout.isRefreshing = true
|
||||
|
||||
// Refresh the canteen menu for the newly selected canteen
|
||||
// refresh()
|
||||
refresh()
|
||||
true
|
||||
}
|
||||
inflate(R.menu.menu_canteens)
|
||||
@@ -92,6 +86,7 @@ class CanteenFragment : AppFragment() {
|
||||
// Set currently selected canteen to the button
|
||||
binding.buttonCanteenPicker.text = getCurrentlySelectedCanteenName()
|
||||
|
||||
// Set up button to display info (most likely opening hours)
|
||||
binding.buttonInfo.setOnClickListener {
|
||||
openBottomSheet(
|
||||
TextSheet(
|
||||
@@ -104,7 +99,6 @@ class CanteenFragment : AppFragment() {
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// Assign a preference value to every button to filter for dietary preferences
|
||||
val chipMap = mapOf(
|
||||
binding.chipPrefFair to DietaryPreferences.WELFARE,
|
||||
@@ -134,8 +128,7 @@ class CanteenFragment : AppFragment() {
|
||||
* 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
|
||||
* TODO the refreshing ability stopped working
|
||||
* TODO order the chips alphabetically?
|
||||
*/
|
||||
val index = binding.chipsPreference.indexOfChild(buttonView)
|
||||
binding.chipsPreference.removeView(buttonView)
|
||||
@@ -144,43 +137,25 @@ class CanteenFragment : AppFragment() {
|
||||
}
|
||||
|
||||
// Set up the view pager's adapter
|
||||
viewPagerAdapter = CanteenOfferPageAdapter(
|
||||
activity as FragmentActivity,
|
||||
)
|
||||
viewPagerAdapter = CanteenOfferPageAdapter(activity as FragmentActivity)
|
||||
|
||||
viewModel.getDateCount().observe(viewLifecycleOwner) { dateCount ->
|
||||
viewPagerAdapter.itemCount = dateCount
|
||||
binding.swipeRefreshLayout.isRefreshing = false
|
||||
// Date count is observed to correctly set the amount of pages and the corresponding tabs
|
||||
viewModel.getDates().observe(viewLifecycleOwner) { newDates ->
|
||||
|
||||
// Retrieve the dates individually and store them for the tab mediator to use
|
||||
dates = newDates.map { it.date }
|
||||
|
||||
// Let the adapter know of the new amount of dates (pages) to display
|
||||
viewPagerAdapter.itemCount = dates.size
|
||||
}
|
||||
|
||||
// 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)
|
||||
// }
|
||||
// Set up TabLayoutMediator to populate tabs
|
||||
TabLayoutMediator(binding.dayTabLayout, binding.viewPager) { tab, position ->
|
||||
tab.text = dates[position]
|
||||
}.attach()
|
||||
|
||||
// Set up functions for when the user swipes to refresh the view
|
||||
binding.swipeRefreshLayout.setOnRefreshListener {
|
||||
@@ -188,23 +163,6 @@ class CanteenFragment : AppFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and attach the object mediating the tabs for the view pager.
|
||||
*
|
||||
* TODO this is error-prone, likes to crash with an IndexOutOfBoundsException, and I have no
|
||||
* fucking idea why
|
||||
*
|
||||
* @param dates titles for the individual tabs
|
||||
*/
|
||||
private fun createTabLayoutMediator(dates: List<String>) {
|
||||
// Only attach the mediator if the list has items, otherwise an error would occur
|
||||
if (dates.isNotEmpty()) {
|
||||
TabLayoutMediator(binding.dayTabLayout, binding.viewPager) { tab, position ->
|
||||
tab.text = dates[position]
|
||||
}.attach()
|
||||
}
|
||||
}
|
||||
|
||||
// Invalidate the view binding
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
@@ -223,7 +181,6 @@ class CanteenFragment : AppFragment() {
|
||||
|
||||
// Update the view
|
||||
viewModel.registerDietaryPreferencesUpdate()
|
||||
// viewPagerAdapter.setNewItems(elements.groupElements().distinct(), dateSize)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -258,38 +215,21 @@ class CanteenFragment : AppFragment() {
|
||||
|
||||
/**
|
||||
* Downloads the canteen offers and refreshes them in the app.
|
||||
* TODO handle SocketTimeoutException
|
||||
*/
|
||||
private fun refresh() {
|
||||
// Retrieve new offers from the website(s)
|
||||
viewModel.fetchOffers(viewModel.preferenceCanteen, onRefreshUpdate = { status ->
|
||||
// TODO refresh updates
|
||||
}, onFinish = {
|
||||
viewModel.fetchOffers(viewModel.preferenceCanteen, onFinish = {
|
||||
/*
|
||||
* 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
|
||||
// createTabLayoutMediator()
|
||||
binding.swipeRefreshLayout.isRefreshing = false
|
||||
|
||||
}, onError = {
|
||||
context?.theme?.showErrorSnackBar(
|
||||
binding.snackbarContainer,
|
||||
getString(R.string.canteen_fetch_error)
|
||||
)
|
||||
binding.swipeRefreshLayout.isRefreshing = false
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
// }
|
||||
}
|
||||
@@ -139,6 +139,8 @@ enum class DietaryPreferences(val value: String) {
|
||||
|
||||
const val TEMPLATE_EMPTY: String = ".........."
|
||||
|
||||
val NONE_MET: Object = construct(TEMPLATE_EMPTY)
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
@@ -44,11 +44,11 @@ class DevCodeSheet(
|
||||
*
|
||||
* About the inks; they are safe (which is suspicious of me to say), just encoded so
|
||||
* people can't just look at the file and figure out all the codes! Of course, they're
|
||||
* easily decoded, but who would go for that? You would have to be a pretty big nerd to
|
||||
* do that. Then again, I was the one who encoded all of these links in the first place,
|
||||
* so I suppose the joke's on me...
|
||||
* easily decoded, but who would go for that? You would have to have a lot of time to
|
||||
* waste to do that. Then again, I was the one who encoded all of these links in the
|
||||
* first place, so I suppose the joke's on me...
|
||||
*
|
||||
* If you're reading this – why?
|
||||
* If you're reading this, I ask you – why?
|
||||
*/
|
||||
when (binding.input.text.toString().uppercase()) {
|
||||
"U1RST0JF".d64 -> launchLink("aHR0cHM6Ly95b3V0dS5iZS90S2k5Wi1mNnFYNA==".d64)
|
||||
@@ -59,6 +59,9 @@ class DevCodeSheet(
|
||||
"SE9ORVNU".d64 -> launchLink("aHR0cHM6Ly95b3V0dS5iZS9DSEg0OE5LUEFraw==".d64)
|
||||
"QkJVT0s/".d64 -> launchLink("aHR0cHM6Ly95b3V0dS5iZS90Rm1PMm1TY0tHNA==".d64)
|
||||
"SUhUU0Mq".d64 -> launchLink("aHR0cHM6Ly95b3V0dS5iZS85bXZ4SVdhWHZuWQ==".d64)
|
||||
"SU5TQU5F".d64 -> launchLink("aHR0cHM6Ly95b3V0dS5iZS90NE9kYTlUZFYwbw==".d64)
|
||||
"X0RSV05f".d64 -> launchLink("aHR0cHM6Ly95b3V0dS5iZS9SUVpUUy1CWjhtcw==".d64)
|
||||
"TUFSR0Uh".d64 -> launchLink("aHR0cHM6Ly95b3V0dS5iZS8tbHFxRHZXRjQ1dw==".d64)
|
||||
"NEVENT" -> { // nuke events
|
||||
nukeEvents()
|
||||
showToast(context, "Nuked all events")
|
||||
|
||||
@@ -2,8 +2,11 @@ package com.denizk0461.studip.viewmodel
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.denizk0461.studip.data.StwParser
|
||||
import com.denizk0461.studip.model.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* View model for [com.denizk0461.studip.fragment.CanteenFragment]
|
||||
@@ -15,21 +18,12 @@ class CanteenViewModel(app: Application) : AppViewModel(app) {
|
||||
// Instantiate a parser, should the user want to refresh the canteen offers
|
||||
private val parser = StwParser(app)
|
||||
|
||||
/**
|
||||
* Retrieves all Stud.IP events.
|
||||
*
|
||||
* @return all Stud.IP events exposed through a LiveData object
|
||||
*/
|
||||
val allOffers: LiveData<List<CanteenOffer>> = repo.allOffers
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves all canteen offer date objects.
|
||||
*
|
||||
* @return a list of instances of canteen offer dates
|
||||
*/
|
||||
fun getDates(): List<OfferDate> = returnBlocking { repo.getDates() }
|
||||
fun getDates(): LiveData<List<OfferDate>> = repo.getDates()
|
||||
|
||||
/**
|
||||
* Retrieves the opening hours of the canteen as a string.
|
||||
@@ -42,24 +36,23 @@ class CanteenViewModel(app: Application) : AppViewModel(app) {
|
||||
* Fetch the canteen offers asynchronously. Updates will be provided through a LiveData object.
|
||||
*
|
||||
* @param canteen canteen from which to fetch offers from
|
||||
* @param onRefreshUpdate action to execute when a status update is available
|
||||
* @param onFinish action to execute when the operation has finished
|
||||
* @param onError action to execute when the operation encountered an error
|
||||
*/
|
||||
fun fetchOffers(canteen: Int, onRefreshUpdate: (status: Int) -> Unit, onFinish: () -> Unit) {
|
||||
doAsync { parser.parse(canteen, onRefreshUpdate, onFinish) }
|
||||
fun fetchOffers(
|
||||
canteen: Int,
|
||||
onFinish: () -> Unit,
|
||||
onError: () -> Unit,
|
||||
) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
parser.parse(canteen, onFinish, onError)
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
*
|
||||
@@ -78,12 +71,6 @@ class CanteenViewModel(app: Application) : AppViewModel(app) {
|
||||
*/
|
||||
fun getPreference(pref: DietaryPreferences): Boolean = repo.getBooleanPreference(pref)
|
||||
|
||||
/**
|
||||
* This value determines whether the user wants to have allergens marked.
|
||||
*/
|
||||
val preferenceAllergen: Boolean
|
||||
get() = repo.getBooleanPreference(SettingsPreferences.ALLERGEN, defaultValue = true)
|
||||
|
||||
/**
|
||||
* This value determines which canteen the user has selected.
|
||||
*/
|
||||
|
||||
@@ -76,6 +76,14 @@
|
||||
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:id="@+id/snackbar_container"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toTopOf="@+id/scroll_view_chip"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
<HorizontalScrollView
|
||||
android:id="@+id/scroll_view_chip"
|
||||
android:layout_width="0dp"
|
||||
|
||||
@@ -417,7 +417,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:text="@string/with_love"
|
||||
android:textSize="10sp"
|
||||
android:textSize="8sp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:textAlignment="center"
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
</string-array>
|
||||
|
||||
<string name="fetch_error_snack">Ein Fehler ist aufgetreten!</string>
|
||||
<string name="canteen_fetch_error">Mensaplan konnte nicht heruntergeladen werden!</string>
|
||||
|
||||
<string name="save">Speichern</string>
|
||||
<string name="delete">Löschen</string>
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
</string-array>
|
||||
|
||||
<string name="fetch_error_snack">Something went wrong!</string>
|
||||
<string name="canteen_fetch_error">Couldn\'t retrieve the canteen plan!</string>
|
||||
|
||||
<string name="save">Save</string>
|
||||
<string name="delete">Delete</string>
|
||||
|
||||
Reference in New Issue
Block a user