Implemented ability to change the fragment opened on app start; made some optimisations to DietaryPreferences.kt. BUG: tab layout mediator is annoying af

This commit is contained in:
denizk0461
2023-04-27 15:15:29 +02:00
parent 1828a4b02e
commit 9d6489f83b
11 changed files with 92 additions and 77 deletions
+17
View File
@@ -1,6 +1,23 @@
<component name="InspectionProjectProfileManager"> <component name="InspectionProjectProfileManager">
<profile version="1.0"> <profile version="1.0">
<option name="myName" value="Project Default" /> <option name="myName" value="Project Default" />
<inspection_tool class="AddConversionCallMigration" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AmbiguousExpressionInWhenBranchMigration" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AndroidLintLambdaLast" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AndroidLintLogConditional" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AndroidLintNegativeMargin" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AndroidLintStringFormatTrivial" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AndroidLintTypos" enabled="true" level="TYPO" enabled_by_default="true" /> <inspection_tool class="AndroidLintTypos" enabled="true" level="TYPO" enabled_by_default="true" />
<inspection_tool class="Destructure" enabled="true" level="INFO" enabled_by_default="true" />
<inspection_tool class="DirectUseOfResultType" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="IncompleteDestructuring" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="KDocMissingDocumentation" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="NoConstructorMigration" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="NonExhaustiveWhenStatementMigration" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PublicApiImplicitType" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="RedundantInnerClassModifier" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ReplaceCollectionCountWithSize" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="ReplaceNotNullAssertionWithElvisReturn" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="WhenWithOnlyElse" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
</profile> </profile>
</component> </component>
@@ -12,6 +12,7 @@ import com.denizk0461.studip.db.AppRepository
import com.denizk0461.studip.fragment.EventFragment import com.denizk0461.studip.fragment.EventFragment
import com.denizk0461.studip.fragment.CanteenFragment import com.denizk0461.studip.fragment.CanteenFragment
import com.denizk0461.studip.fragment.SettingsFragment import com.denizk0461.studip.fragment.SettingsFragment
import com.denizk0461.studip.model.SettingsPreferences
/** /**
* Main activity that handles all common fragments. This is opened on app launch. * Main activity that handles all common fragments. This is opened on app launch.
@@ -41,9 +42,21 @@ class MainActivity : AppCompatActivity() {
binding = ActivityMainBinding.inflate(layoutInflater) binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
// Open the fragment that the user specified to open on app start
if (
Dependencies.repo.getBooleanPreference(SettingsPreferences.LAUNCH_CANTEEN_ON_START)
) {
// Open canteen fragment
binding.contentMain.navView.selectedItemId = R.id.food
currentFragment = "canteen"
} else {
// Open event fragment
binding.contentMain.navView.selectedItemId = R.id.plan
currentFragment = "event"
}
// Set up navigation view bar to launch fragments // Set up navigation view bar to launch fragments
binding.contentMain.navView.setOnItemSelectedListener { item -> binding.contentMain.navView.setOnItemSelectedListener { item ->
// Launch fragment based on the item that has been clicked // Launch fragment based on the item that has been clicked
loadFragment(when (item.itemId) { loadFragment(when (item.itemId) {
R.id.food -> "canteen" R.id.food -> "canteen"
@@ -68,11 +68,13 @@ class CanteenOfferItemAdapter(
// Inflate a new icon holder // Inflate a new icon holder
val img = ItemIconBinding.inflate(LayoutInflater.from(holder.binding.root.context)) val img = ItemIconBinding.inflate(LayoutInflater.from(holder.binding.root.context))
val (_, drawableId) = DietaryPreferences.getData(DietaryPreferences.ERROR.ordinal)
// Set a cross icon // Set a cross icon
img.imageView.setImageDrawable( img.imageView.setImageDrawable(
AppCompatResources.getDrawable( AppCompatResources.getDrawable(
holder.binding.root.context, holder.binding.root.context,
DietaryPreferences.indexToDrawable[11]!!, drawableId,
) )
) )
@@ -117,11 +119,13 @@ class CanteenOfferItemAdapter(
// Inflate a new icon holder // Inflate a new icon holder
val img = ItemIconBinding.inflate(LayoutInflater.from(holder.binding.root.context)) val img = ItemIconBinding.inflate(LayoutInflater.from(holder.binding.root.context))
val (_, drawableId) = DietaryPreferences.getData(index)
// Set the appropriate icon // Set the appropriate icon
img.imageView.setImageDrawable( img.imageView.setImageDrawable(
AppCompatResources.getDrawable( AppCompatResources.getDrawable(
holder.binding.root.context, holder.binding.root.context,
DietaryPreferences.indexToDrawable[index]!!, drawableId,
) )
) )
@@ -1,6 +1,5 @@
package com.denizk0461.studip.data package com.denizk0461.studip.data
import android.util.Log
import com.denizk0461.studip.db.AppRepository import com.denizk0461.studip.db.AppRepository
import com.denizk0461.studip.model.* import com.denizk0461.studip.model.*
import org.jsoup.Jsoup import org.jsoup.Jsoup
@@ -63,19 +62,10 @@ class StwParser {
// Delete all previous entries and start afresh // Delete all previous entries and start afresh
Dependencies.repo.nukeOffers() Dependencies.repo.nukeOffers()
/* // Fetch offers from the chosen canteen
* Fetch offers from every canteen individually. This is done in sequence for parseFromPage(link, Dependencies.repo)
* simplicity's sake.
* TODO perhaps it can be assumed here that the next two weeks will always be available?
* Hence, the date fetch might be superfluous and a database insert call could be
* implemented here instead. Another option would be to set the conflict policy to IGNORE
* and move the dateId reset from parseFromPage() to parse().
*/
// links.forEach { link ->
parseFromPage(link, Dependencies.repo)
// TODO implement onRefresh(Int) // TODO implement onRefresh(Int)
// }
// Action call once all fetching activities have finished // Action call once all fetching activities have finished
onFinish() onFinish()
@@ -175,8 +165,6 @@ class StwParser {
// Retrieve the category text // Retrieve the category text
val categoryTitle = category.getElementsByClass("category-name")[0].text() val categoryTitle = category.getElementsByClass("category-name")[0].text()
Log.d("AAA?", "$date on $categoryTitle")
// Save the category to persistent storage // Save the category to persistent storage
repo.insert(OfferCategory(categoryId, dateId, canteenId, categoryTitle)) repo.insert(OfferCategory(categoryId, dateId, canteenId, categoryTitle))
@@ -152,7 +152,7 @@ class CanteenFragment : AppFragment(), CanteenOfferItemAdapter.OnClickListener {
binding.viewPager.adapter = viewPagerAdapter binding.viewPager.adapter = viewPagerAdapter
// Create and attach the tab mediator for the view pager // Create and attach the tab mediator for the view pager
createTabLayoutMediator() // createTabLayoutMediator()
// Set up LiveData observer to refresh the view on update // Set up LiveData observer to refresh the view on update
viewModel.allOffers.observe(viewLifecycleOwner) { offers -> viewModel.allOffers.observe(viewLifecycleOwner) { offers ->
@@ -163,7 +163,7 @@ class CanteenFragment : AppFragment(), CanteenOfferItemAdapter.OnClickListener {
val groupedElements = offers.groupElements().distinct() val groupedElements = offers.groupElements().distinct()
// Find all dates stored in the database // Find all dates stored in the database
val newDates = groupedElements.map { it.date }.distinct() // TODO viewModel.getDates() val newDates = /*groupedElements.map { it.date }.distinct() TODO */viewModel.getDates()
// Update the date count stored in this fragment // Update the date count stored in this fragment
dateSize = newDates.size dateSize = newDates.size
@@ -173,6 +173,10 @@ class CanteenFragment : AppFragment(), CanteenOfferItemAdapter.OnClickListener {
// Set the text for the opening hours dialogue // Set the text for the opening hours dialogue
openingHours = viewModel.getCanteenOpeningHours() openingHours = viewModel.getCanteenOpeningHours()
binding.swipeRefreshLayout.isRefreshing = false
// createTabLayoutMediator(newDates)
} }
// Set up functions for when the user swipes to refresh the view // Set up functions for when the user swipes to refresh the view
@@ -185,10 +189,11 @@ class CanteenFragment : AppFragment(), CanteenOfferItemAdapter.OnClickListener {
* Create and attach the object mediating the tabs for the view pager. * Create and attach the object mediating the tabs for the view pager.
* TODO this is crash-prone and doesn't refresh when the dates get renewed (when the last update * TODO this is crash-prone and doesn't refresh when the dates get renewed (when the last update
* was from the day prior) * was from the day prior)
* TODO WHY DO YOU THROW AN INDEXOUTOFBOUNDSEXCEPTION????
*/ */
private fun createTabLayoutMediator() { private fun createTabLayoutMediator(dates: List<OfferDate>) {
// Fetch all dates from the database // Fetch all dates from the database
val dates = viewModel.getDates() // val dates = viewModel.getDates()
if (dates.isNotEmpty()) { if (dates.isNotEmpty()) {
TabLayoutMediator(binding.dayTabLayout, binding.viewPager) { tab, position -> TabLayoutMediator(binding.dayTabLayout, binding.viewPager) { tab, position ->
@@ -340,6 +345,7 @@ class CanteenFragment : AppFragment(), CanteenOfferItemAdapter.OnClickListener {
/** /**
* Downloads the canteen offers and refreshes them in the app. * Downloads the canteen offers and refreshes them in the app.
* TODO handle SocketTimeoutException
*/ */
private fun refresh() { private fun refresh() {
// Retrieve new offers from the website(s) // Retrieve new offers from the website(s)
@@ -350,10 +356,6 @@ class CanteenFragment : AppFragment(), CanteenOfferItemAdapter.OnClickListener {
* Tell the swipe refresh layout to stop refreshing. * Tell the swipe refresh layout to stop refreshing.
* TODO if an exception is raised, this will not be fired. This must be changed * TODO if an exception is raised, this will not be fired. This must be changed
*/ */
binding.swipeRefreshLayout.isRefreshing = false
// createTabLayoutMediator()
// binding.swipeRefreshLayout.isRefreshing = false // binding.swipeRefreshLayout.isRefreshing = false
// createTabLayoutMediator() // createTabLayoutMediator()
}) })
@@ -21,46 +21,28 @@ enum class DietaryPreferences(val value: String) {
VEGAN("isVegan"), VEGAN("isVegan"),
VEGETARIAN("isVegetarian"), VEGETARIAN("isVegetarian"),
GAME("isGame"), GAME("isGame"),
NONE("hasNone"),
ERROR("onError")
; ;
companion object { companion object {
/**
* Provides a converting function between the index of a dietary preference and its according
* icon.
* TODO this seems inefficient
*/
val indexToDrawable: Map<Int, Int> = mapOf(
0 to R.drawable.handshake,
1 to R.drawable.fish,
2 to R.drawable.chicken,
3 to R.drawable.sheep,
4 to R.drawable.yoga,
5 to R.drawable.cow,
6 to R.drawable.pig,
7 to R.drawable.leaf,
8 to R.drawable.carrot,
9 to R.drawable.deer,
10 to R.drawable.circle,
11 to R.drawable.cross,
)
/** /**
* Provides a converting function between the index of a dietary preference and its * Provides a converting function between
* according localised text string.
* TODO this seems inefficient
*/ */
val indexToString: Map<Int, Int> = mapOf( fun getData(index: Int): Pair<Int, Int> = when (index) {
0 to R.string.pref_fair, FAIR.ordinal -> Pair(R.string.pref_fair, R.drawable.handshake)
1 to R.string.pref_fish, FISH.ordinal -> Pair(R.string.pref_fish, R.drawable.fish)
2 to R.string.pref_poultry, POULTRY.ordinal -> Pair(R.string.pref_poultry, R.drawable.chicken)
3 to R.string.pref_lamb, LAMB.ordinal -> Pair(R.string.pref_lamb, R.drawable.sheep)
4 to R.string.pref_vital, VITAL.ordinal -> Pair(R.string.pref_vital, R.drawable.yoga)
5 to R.string.pref_beef, BEEF.ordinal -> Pair(R.string.pref_beef, R.drawable.cow)
6 to R.string.pref_pork, PORK.ordinal -> Pair(R.string.pref_pork, R.drawable.pig)
7 to R.string.pref_vegan, VEGAN.ordinal -> Pair(R.string.pref_vegan, R.drawable.leaf)
8 to R.string.pref_vegetarian, VEGETARIAN.ordinal -> Pair(R.string.pref_vegetarian, R.drawable.carrot)
9 to R.string.pref_game, GAME.ordinal -> Pair(R.string.pref_game, R.drawable.deer)
10 to 0, // shouldn't occur NONE.ordinal -> Pair(R.string.question_mark, R.drawable.circle)
) else -> Pair(R.string.question_mark, R.drawable.cross) // ERROR.ordinal
}
} }
} }
@@ -45,14 +45,16 @@ class AllergenSheet(
// Inflate the view // Inflate the view
val prefLine = ItemSheetPreferenceBinding.inflate(LayoutInflater.from(context)) val prefLine = ItemSheetPreferenceBinding.inflate(LayoutInflater.from(context))
val (stringId, drawableId) = DietaryPreferences.getData(index)
// Add the localised text for the preference // Add the localised text for the preference
prefLine.preferenceText.text = getString(DietaryPreferences.indexToString[index]!!) prefLine.preferenceText.text = getString(stringId)
// Set the appropriate icon // Set the appropriate icon
prefLine.preferenceImage.setImageDrawable( prefLine.preferenceImage.setImageDrawable(
getDrawable( getDrawable(
context, context,
DietaryPreferences.indexToDrawable[index]!!, drawableId,
) )
) )
@@ -5,7 +5,6 @@ import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.util.Base64 import android.util.Base64
import android.view.View import android.view.View
import android.widget.Toast
import com.denizk0461.studip.R import com.denizk0461.studip.R
import com.denizk0461.studip.data.showToast import com.denizk0461.studip.data.showToast
import com.denizk0461.studip.data.viewBinding import com.denizk0461.studip.data.viewBinding
@@ -52,13 +51,14 @@ class DevCodeSheet(
* If you're reading this why? * If you're reading this why?
*/ */
when (binding.input.text.toString().uppercase()) { when (binding.input.text.toString().uppercase()) {
"U1RST0JF".d() -> launchLink("aHR0cHM6Ly95b3V0dS5iZS90S2k5Wi1mNnFYNA==".d()) "U1RST0JF".d64 -> launchLink("aHR0cHM6Ly95b3V0dS5iZS90S2k5Wi1mNnFYNA==".d64)
"U1BJUklU".d() -> launchLink("aHR0cHM6Ly95b3V0dS5iZS9XN25lMzlEU05TZw==".d()) "U1BJUklU".d64 -> launchLink("aHR0cHM6Ly95b3V0dS5iZS9XN25lMzlEU05TZw==".d64)
"Q0xPVURT".d() -> launchLink("aHR0cHM6Ly95b3V0dS5iZS9rbUJ6YTRhNTB2dw==".d()) "Q0xPVURT".d64 -> launchLink("aHR0cHM6Ly95b3V0dS5iZS9rbUJ6YTRhNTB2dw==".d64)
"NjQxODJL".d() -> launchLink("aHR0cHM6Ly95b3V0dS5iZS9EbTFyZ285UHBUcw==".d()) "NjQxODJL".d64 -> launchLink("aHR0cHM6Ly95b3V0dS5iZS9EbTFyZ285UHBUcw==".d64)
"U0FJTE9S".d() -> launchLink("aHR0cHM6Ly95b3V0dS5iZS9QUHlDYXZ6OG5NWQ==".d()) "U0FJTE9S".d64 -> launchLink("aHR0cHM6Ly95b3V0dS5iZS9QUHlDYXZ6OG5NWQ==".d64)
"SE9ORVNU".d() -> launchLink("aHR0cHM6Ly95b3V0dS5iZS9DSEg0OE5LUEFraw==".d()) "SE9ORVNU".d64 -> launchLink("aHR0cHM6Ly95b3V0dS5iZS9DSEg0OE5LUEFraw==".d64)
"QkJVT0s/".d() -> launchLink("aHR0cHM6Ly95b3V0dS5iZS90Rm1PMm1TY0tHNA==".d()) "QkJVT0s/".d64 -> launchLink("aHR0cHM6Ly95b3V0dS5iZS90Rm1PMm1TY0tHNA==".d64)
"SUhUU0Mq".d64 -> launchLink("aHR0cHM6Ly95b3V0dS5iZS85bXZ4SVdhWHZuWQ==".d64)
"NEVENT" -> { // nuke events "NEVENT" -> { // nuke events
nukeEvents() nukeEvents()
showToast(context, "Nuked all events") showToast(context, "Nuked all events")
@@ -107,7 +107,7 @@ class DevCodeSheet(
* *
* @return the encoded string * @return the encoded string
*/ */
private fun String.e(): String = Base64.encodeToString(this.toByteArray(), Base64.DEFAULT) private val String.e64: String get() = Base64.encodeToString(this.toByteArray(), Base64.DEFAULT)
/** /**
@@ -115,7 +115,7 @@ class DevCodeSheet(
* *
* @return the decoded string * @return the decoded string
*/ */
private fun String.d(): String = String( private val String.d64: String get() = String(
Base64.decode(this, Base64.DEFAULT), Base64.decode(this, Base64.DEFAULT),
StandardCharsets.UTF_8 StandardCharsets.UTF_8
) )
@@ -161,10 +161,12 @@ class ScheduleUpdateSheet(
// Parse the separate ints into a timestamp string. Add a leading zero if necessary. // Parse the separate ints into a timestamp string. Add a leading zero if necessary.
val newTimestamp = "$hours:${String.format("%02d", minutes)}" val newTimestamp = "$hours:${String.format("%02d", minutes)}"
// If start > end, show an error if (isEventStart && newTimestamp.parseToMinutes() > timeslotEnd.parseToMinutes()) {
if ((isEventStart && newTimestamp.parseToMinutes() > timeslotEnd.parseToMinutes()) || // If editing start and start > end, show an error
(!isEventStart && newTimestamp.parseToMinutes() < timeslotStart.parseToMinutes())) { showToast(context, getString(R.string.sheet_schedule_update_timestamp_error_start))
// TODO tell user that the timestamp must be set earlier/later // If editing end and end < start, show an error
} else if (!isEventStart && newTimestamp.parseToMinutes() < timeslotStart.parseToMinutes()) {
showToast(context, getString(R.string.sheet_schedule_update_timestamp_error_end))
} else { } else {
// If the new timestamp is valid, save it // If the new timestamp is valid, save it
if (isEventStart) { if (isEventStart) {
+2
View File
@@ -21,6 +21,8 @@
<string name="sheet_schedule_update_room_hint">Raum</string> <string name="sheet_schedule_update_room_hint">Raum</string>
<string name="sheet_schedule_update_hint_quarter">Auf akademisches Viertel umstellen</string> <string name="sheet_schedule_update_hint_quarter">Auf akademisches Viertel umstellen</string>
<string name="sheet_schedule_update_hint_quarter_error">Sorry, ein Fehler ist aufgetreten!</string> <string name="sheet_schedule_update_hint_quarter_error">Sorry, ein Fehler ist aufgetreten!</string>
<string name="sheet_schedule_update_timestamp_error_start">Der Kurs kann nicht anfangen nachdem er aufhört!</string>
<string name="sheet_schedule_update_timestamp_error_end">Der Kurs kann nicht enden bevor er anfängt!</string>
<string name="canteen_no_offer_header">Kein Angebot</string> <string name="canteen_no_offer_header">Kein Angebot</string>
<string name="canteen_no_offer_desc">Für diesen Tag sind keine Angebote vorhanden</string> <string name="canteen_no_offer_desc">Für diesen Tag sind keine Angebote vorhanden</string>
+4 -1
View File
@@ -22,6 +22,8 @@
<string name="sheet_schedule_update_timeslot_middle" translatable="false"></string> <string name="sheet_schedule_update_timeslot_middle" translatable="false"></string>
<string name="sheet_schedule_update_hint_quarter">Change to academic quarter</string> <string name="sheet_schedule_update_hint_quarter">Change to academic quarter</string>
<string name="sheet_schedule_update_hint_quarter_error">Sorry, an error occurred!</string> <string name="sheet_schedule_update_hint_quarter_error">Sorry, an error occurred!</string>
<string name="sheet_schedule_update_timestamp_error_start">The course can\'t begin after it ends!</string>
<string name="sheet_schedule_update_timestamp_error_end">The course can\'t end before it begins!</string>
<string name="canteen_no_offer_header">No offers</string> <string name="canteen_no_offer_header">No offers</string>
<string name="canteen_no_offer_desc">There are no offers available for this day</string> <string name="canteen_no_offer_desc">There are no offers available for this day</string>
@@ -47,9 +49,10 @@
<string name="mensa_hfk">Interimsmensa HfK</string> <string name="mensa_hfk">Interimsmensa HfK</string>
<string name="sheet_category" translatable="false">[ %s ]</string> <string name="sheet_category" translatable="false">[ %s ]</string>
<string name="question_mark" translatable="false">\?</string>
<!-- dietary preferences --> <!-- dietary preferences -->
<string name="pref_fair">Fair</string> <!-- is this really the best name? what about 'animal welfare'? --> <string name="pref_fair">Animal Welfare</string> <!-- TODO is this really the best name? what about 'animal welfare'? -->
<string name="pref_fish">Fish</string> <string name="pref_fish">Fish</string>
<string name="pref_poultry">Poultry</string> <string name="pref_poultry">Poultry</string>
<string name="pref_lamb">Lamb</string> <string name="pref_lamb">Lamb</string>