Archived
Further error handling. Also given the user the option to colour dietary preference icons. Colours have partially been derived from those used on stw-bremen.de but have all been processed through the theme builder on m3.material.io
This commit is contained in:
@@ -6,6 +6,7 @@ import android.os.Bundle
|
||||
import android.webkit.WebResourceRequest
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import android.widget.Toast
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.denizk0461.studip.R
|
||||
import com.denizk0461.studip.data.showErrorSnackBar
|
||||
@@ -60,38 +61,37 @@ class FetcherActivity : Activity() {
|
||||
binding.webview.loadUrl("https://elearning.uni-bremen.de/index.php?again=yes")
|
||||
|
||||
binding.fab.setOnClickListener { view ->
|
||||
/*
|
||||
* Retrieve encoded HTML via JavaScript function. Encoding is done as otherwise not all
|
||||
* symbols will be accurately fetched.
|
||||
*/
|
||||
binding.webview.evaluateJavascript(
|
||||
"(function(){return encodeURI(document.getElementsByTagName('html')[0].innerHTML)})();"
|
||||
) { p0 ->
|
||||
try {
|
||||
// Decode HTML and parse it into a list of StudIPEvent.kt
|
||||
// TODO this exception handling and finish() is broken
|
||||
viewModel.parse(URLDecoder.decode(p0, "UTF-8"))
|
||||
// StudIPParser(application).parse(URLDecoder.decode(p0, "UTF-8")) { events ->
|
||||
//
|
||||
// // Delete all previously fetched elements
|
||||
//// viewModel.nukeEvents()
|
||||
//// viewModel.replaceEvents(events)
|
||||
//
|
||||
// // Insert the list into the database
|
||||
//// viewModel.insertEvents(events)
|
||||
//
|
||||
// // Notify the user that the fetch was successful
|
||||
// Toast.makeText(
|
||||
// this, R.string.toast_fetch_finished, Toast.LENGTH_SHORT
|
||||
// ).show()
|
||||
//
|
||||
// // Close the activity
|
||||
// finish()
|
||||
// }
|
||||
} catch (e: IOException) {
|
||||
// Let the user know that an error occurred
|
||||
theme.showErrorSnackBar(binding.rootView, getString(R.string.fetch_error_snack))
|
||||
|
||||
if (binding.webview.url?.contains(
|
||||
"https://elearning.uni-bremen.de/dispatch.php/calendar/schedule"
|
||||
) == true) {
|
||||
/*
|
||||
* Retrieve encoded HTML via JavaScript function. Encoding is done as otherwise not all
|
||||
* symbols will be accurately fetched.
|
||||
*/
|
||||
binding.webview.evaluateJavascript(
|
||||
"(function(){return encodeURI(document.getElementsByTagName('html')[0].innerHTML)})();"
|
||||
) { p0 ->
|
||||
try {
|
||||
// Decode HTML and parse it into a list of StudIPEvent.kt
|
||||
// TODO add a tutorial for the user to know what to do
|
||||
viewModel.parse(URLDecoder.decode(p0, "UTF-8"))
|
||||
|
||||
// Notify the user that the fetch was successful
|
||||
Toast.makeText(
|
||||
this, R.string.toast_fetch_finished, Toast.LENGTH_SHORT
|
||||
).show()
|
||||
|
||||
// Close the activity
|
||||
finish()
|
||||
|
||||
} catch (e: IOException) {
|
||||
// Let the user know that an error occurred
|
||||
theme.showErrorSnackBar(binding.rootView, getString(R.string.fetch_error_snack))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
theme.showErrorSnackBar(binding.rootView, getString(R.string.fetch_error_webpage_snack))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.denizk0461.studip.adapter
|
||||
|
||||
import android.content.res.ColorStateList
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
@@ -7,6 +8,7 @@ import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.denizk0461.studip.R
|
||||
import com.denizk0461.studip.data.AppDiffUtilCallback
|
||||
import com.denizk0461.studip.data.getThemedColor
|
||||
import com.denizk0461.studip.databinding.ItemCanteenBinding
|
||||
import com.denizk0461.studip.databinding.ItemCanteenLineBinding
|
||||
import com.denizk0461.studip.databinding.ItemIconBinding
|
||||
@@ -19,10 +21,12 @@ import com.denizk0461.studip.model.DietaryPreferences
|
||||
*
|
||||
* @param onClickListener used for listening to clicks and long presses
|
||||
* @param displayAllergens whether the user wants allergens to be marked
|
||||
* @param displayColours whether the user wants dietary preferences to be marked with colours
|
||||
*/
|
||||
class CanteenOfferItemAdapter(
|
||||
private val onClickListener: OnClickListener,
|
||||
private val displayAllergens: Boolean,
|
||||
private val displayColours: Boolean,
|
||||
) : RecyclerView.Adapter<CanteenOfferItemAdapter.OfferViewHolder>() {
|
||||
|
||||
private val offers: MutableList<CanteenOfferGroup> = mutableListOf()
|
||||
@@ -79,7 +83,8 @@ class CanteenOfferItemAdapter(
|
||||
// Inflate a new icon holder
|
||||
val img = ItemIconBinding.inflate(LayoutInflater.from(holder.binding.root.context))
|
||||
|
||||
val (_, drawableId) = DietaryPreferences.getData(DietaryPreferences.ERROR.ordinal)
|
||||
// Retrieve data for the dietary preference
|
||||
val (_, drawableId, colourId) = DietaryPreferences.getData(DietaryPreferences.ERROR.ordinal)
|
||||
|
||||
// Set a cross icon
|
||||
img.imageView.setImageDrawable(
|
||||
@@ -89,6 +94,17 @@ class CanteenOfferItemAdapter(
|
||||
)
|
||||
)
|
||||
|
||||
// Set tint of the icon; themed to the preference, if the user has the option enabled
|
||||
img.imageView.imageTintList = ColorStateList.valueOf(
|
||||
holder.binding.root.context.theme.getThemedColor(
|
||||
if (displayColours) {
|
||||
colourId
|
||||
} else {
|
||||
R.attr.colorText
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
// Bind the new icon to the line view
|
||||
line.imageViewContainer.addView(img.root)
|
||||
|
||||
@@ -130,7 +146,8 @@ class CanteenOfferItemAdapter(
|
||||
// Inflate a new icon holder
|
||||
val img = ItemIconBinding.inflate(LayoutInflater.from(holder.binding.root.context))
|
||||
|
||||
val (_, drawableId) = DietaryPreferences.getData(index)
|
||||
// Retrieve data for the dietary preference
|
||||
val (_, drawableId, colourId) = DietaryPreferences.getData(index)
|
||||
|
||||
// Set the appropriate icon
|
||||
img.imageView.setImageDrawable(
|
||||
@@ -140,6 +157,17 @@ class CanteenOfferItemAdapter(
|
||||
)
|
||||
)
|
||||
|
||||
// Set tint of the icon
|
||||
img.imageView.imageTintList = ColorStateList.valueOf(
|
||||
holder.binding.root.context.theme.getThemedColor(
|
||||
if (displayColours) {
|
||||
colourId
|
||||
} else {
|
||||
R.attr.colorText
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
// Bind the new icon to the line view
|
||||
line.imageViewContainer.addView(img.root)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.denizk0461.studip.db.AppRepository
|
||||
import com.denizk0461.studip.model.StudIPEvent
|
||||
import org.jsoup.Jsoup
|
||||
import java.io.IOException
|
||||
import kotlin.jvm.Throws
|
||||
|
||||
/**
|
||||
* Parser class used for fetching and collecting scheduled events from a Stud.IP timetable.
|
||||
@@ -17,11 +18,10 @@ class StudIPParser(application: Application) {
|
||||
* Parse a given HTML string. HTML must be of a Stud.IP timetable.
|
||||
*
|
||||
* @param html website content of the Stud.IP timetable
|
||||
* @param insert action call to save the contents to persistent storage
|
||||
* @throws IOException when an error in fetching or the database transaction occurs
|
||||
*/
|
||||
@kotlin.jvm.Throws(IOException::class)
|
||||
fun parse(html: String, insert: (events: List<StudIPEvent>) -> Unit) {
|
||||
@Throws(IOException::class)
|
||||
fun parse(html: String) {
|
||||
// Primary key value to uniquely identify entries in the database
|
||||
var id = 0
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ 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()
|
||||
|
||||
@@ -54,6 +54,7 @@ class CanteenPageFragment(
|
||||
recyclerViewAdapter = CanteenOfferItemAdapter(
|
||||
this,
|
||||
viewModel.preferenceAllergen,
|
||||
viewModel.preferenceColour,
|
||||
)
|
||||
|
||||
binding.recyclerView.apply {
|
||||
@@ -62,13 +63,6 @@ class CanteenPageFragment(
|
||||
context, LinearLayoutManager.VERTICAL, false
|
||||
)
|
||||
|
||||
/*
|
||||
* Create new adapter for every page. Attribute position denotes day that will be set up
|
||||
* by the newly created adapter (0 = Monday, 4 = Friday)
|
||||
* TODO check if the filtered list is empty, and tell the user if it is
|
||||
*/
|
||||
// val o = offers.filter { it.dateId == currentDay }
|
||||
|
||||
adapter = recyclerViewAdapter
|
||||
|
||||
// Animate creation of new page
|
||||
@@ -78,7 +72,6 @@ class CanteenPageFragment(
|
||||
offerList.clear()
|
||||
offerList.addAll(offers)
|
||||
recyclerViewAdapter.setNewData(offers.filterElements().groupElements().distinct())
|
||||
// filterList()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,7 +185,7 @@ class CanteenPageFragment(
|
||||
* @param offer item that has been clicked
|
||||
*/
|
||||
override fun onClick(offer: CanteenOfferGroupElement, category: String) {
|
||||
openBottomSheet(AllergenSheet(offer, category))
|
||||
openBottomSheet(AllergenSheet(offer, category, viewModel.preferenceColour))
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -79,6 +79,14 @@ class SettingsFragment : AppFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
// Set up switch for colouring dietary preferences
|
||||
binding.switchPrefColour.apply {
|
||||
isChecked = viewModel.preferenceColour
|
||||
setOnCheckedChangeListener { _, newValue ->
|
||||
viewModel.preferenceColour = newValue
|
||||
}
|
||||
}
|
||||
|
||||
// Set up switch for crash report opt-in
|
||||
binding.switchCrashlytics.apply {
|
||||
isChecked = viewModel.preferenceDataHandling
|
||||
|
||||
@@ -167,21 +167,21 @@ enum class DietaryPreferences(val value: String) {
|
||||
* string and drawable values.
|
||||
*
|
||||
* @param index ordinal of the enum item
|
||||
* @return string resource ID and drawable resource ID as a pair
|
||||
* @return string resource ID, drawable resource ID, colour attribute ID
|
||||
*/
|
||||
fun getData(index: Int): Pair<Int, Int> = when (index) {
|
||||
WELFARE.ordinal -> Pair(R.string.pref_fair, R.drawable.handshake)
|
||||
FISH.ordinal -> Pair(R.string.pref_fish, R.drawable.fish)
|
||||
POULTRY.ordinal -> Pair(R.string.pref_poultry, R.drawable.chicken)
|
||||
LAMB.ordinal -> Pair(R.string.pref_lamb, R.drawable.sheep)
|
||||
VITAL.ordinal -> Pair(R.string.pref_vital, R.drawable.yoga)
|
||||
BEEF.ordinal -> Pair(R.string.pref_beef, R.drawable.cow)
|
||||
PORK.ordinal -> Pair(R.string.pref_pork, R.drawable.pig)
|
||||
VEGAN.ordinal -> Pair(R.string.pref_vegan, R.drawable.leaf)
|
||||
VEGETARIAN.ordinal -> Pair(R.string.pref_vegetarian, R.drawable.carrot)
|
||||
GAME.ordinal -> Pair(R.string.pref_game, R.drawable.deer)
|
||||
NONE.ordinal -> Pair(R.string.question_mark, R.drawable.circle)
|
||||
else -> Pair(R.string.question_mark, R.drawable.cross) // ERROR.ordinal
|
||||
fun getData(index: Int): Triple<Int, Int, Int> = when (index) {
|
||||
WELFARE.ordinal -> Triple(R.string.pref_fair, R.drawable.handshake, R.attr.colorPrimaryFair)
|
||||
FISH.ordinal -> Triple(R.string.pref_fish, R.drawable.fish, R.attr.colorPrimaryFish)
|
||||
POULTRY.ordinal -> Triple(R.string.pref_poultry, R.drawable.chicken, R.attr.colorPrimaryPoultry)
|
||||
LAMB.ordinal -> Triple(R.string.pref_lamb, R.drawable.sheep, R.attr.colorPrimaryLamb)
|
||||
VITAL.ordinal -> Triple(R.string.pref_vital, R.drawable.yoga, R.attr.colorPrimaryVital)
|
||||
BEEF.ordinal -> Triple(R.string.pref_beef, R.drawable.cow, R.attr.colorPrimaryBeef)
|
||||
PORK.ordinal -> Triple(R.string.pref_pork, R.drawable.pig, R.attr.colorPrimaryPork)
|
||||
VEGAN.ordinal -> Triple(R.string.pref_vegan, R.drawable.leaf, R.attr.colorPrimaryVegan)
|
||||
VEGETARIAN.ordinal -> Triple(R.string.pref_vegetarian, R.drawable.carrot, R.attr.colorPrimaryVegetarian)
|
||||
GAME.ordinal -> Triple(R.string.pref_game, R.drawable.deer, R.attr.colorPrimaryGame)
|
||||
NONE.ordinal -> Triple(R.string.question_mark, R.drawable.circle, R.attr.colorText)
|
||||
else -> Triple(R.string.question_mark, R.drawable.cross, R.attr.colorErrorText) // ERROR.ordinal
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,11 +17,17 @@ enum class SettingsPreferences(val key: String) {
|
||||
* Whether the user wants the next course in his schedule to be highlighted.
|
||||
*/
|
||||
COURSE_HIGHLIGHTING("setting_highlight"),
|
||||
|
||||
/**
|
||||
* Whether the user wants the app to launch with the canteen fragment.
|
||||
*/
|
||||
LAUNCH_CANTEEN_ON_START("settings_launch_canteen"),
|
||||
|
||||
/**
|
||||
* Whether the user wants dietary preferences to be coloured.
|
||||
*/
|
||||
COLOUR_PREFS("settings_prefs_colour"),
|
||||
|
||||
/**
|
||||
* Whether the user opts into sending crash report.
|
||||
*/
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package com.denizk0461.studip.sheet
|
||||
|
||||
import android.content.res.ColorStateList
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import androidx.appcompat.content.res.AppCompatResources.getDrawable
|
||||
import com.denizk0461.studip.R
|
||||
import com.denizk0461.studip.data.getThemedColor
|
||||
import com.denizk0461.studip.data.viewBinding
|
||||
import com.denizk0461.studip.databinding.ItemSheetPreferenceBinding
|
||||
import com.denizk0461.studip.databinding.SheetAllergenBinding
|
||||
@@ -18,10 +20,12 @@ import com.denizk0461.studip.model.DietaryPreferences
|
||||
*
|
||||
* @param offer item to display further information on
|
||||
* @param category category the offer belongs to
|
||||
* @param displayColours whether the user wants dietary preferences to be marked with colours
|
||||
*/
|
||||
class AllergenSheet(
|
||||
private val offer: CanteenOfferGroupElement,
|
||||
private val category: String,
|
||||
private val displayColours: Boolean,
|
||||
) : AppSheet(R.layout.sheet_allergen) {
|
||||
|
||||
// View binding
|
||||
@@ -45,7 +49,7 @@ class AllergenSheet(
|
||||
// Inflate the view
|
||||
val prefLine = ItemSheetPreferenceBinding.inflate(LayoutInflater.from(context))
|
||||
|
||||
val (stringId, drawableId) = DietaryPreferences.getData(index)
|
||||
val (stringId, drawableId, colourId) = DietaryPreferences.getData(index)
|
||||
|
||||
// Add the localised text for the preference
|
||||
prefLine.preferenceText.text = getString(stringId)
|
||||
@@ -58,6 +62,17 @@ class AllergenSheet(
|
||||
)
|
||||
)
|
||||
|
||||
// Set tint of the icon; themed to the preference, if the user has the option enabled
|
||||
prefLine.preferenceImage.imageTintList = ColorStateList.valueOf(
|
||||
context.theme.getThemedColor(
|
||||
if (displayColours) {
|
||||
colourId
|
||||
} else {
|
||||
R.attr.colorText
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
// Add the view to the sheet's view container
|
||||
containerPreferences.addView(prefLine.root)
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ class DevCodeSheet(
|
||||
"SU5TQU5F".d64 -> launchLink("aHR0cHM6Ly95b3V0dS5iZS90NE9kYTlUZFYwbw==".d64)
|
||||
"X0RSV05f".d64 -> launchLink("aHR0cHM6Ly95b3V0dS5iZS9SUVpUUy1CWjhtcw==".d64)
|
||||
"TUFSR0Uh".d64 -> launchLink("aHR0cHM6Ly95b3V0dS5iZS8tbHFxRHZXRjQ1dw==".d64)
|
||||
"UE9XRUxM".d64 -> launchLink("aHR0cHM6Ly95b3V0dS5iZS9XUGMtVkVxQlBISQ==".d64)
|
||||
"NEVENT" -> { // nuke events
|
||||
nukeEvents()
|
||||
showToast(context, "Nuked all events")
|
||||
@@ -86,7 +87,8 @@ class DevCodeSheet(
|
||||
nukeEverything()
|
||||
showToast(context, "Nuked everything")
|
||||
}
|
||||
else -> showToast(context, "code is invalid")
|
||||
"HACK()" -> showToast(context, "Hack attempt unsuccessful")
|
||||
else -> showToast(context, "Code is invalid")
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
|
||||
@@ -30,4 +30,11 @@ class CanteenPageViewModel(app: Application) : AppViewModel(app) {
|
||||
*/
|
||||
val preferenceAllergen: Boolean
|
||||
get() = repo.getBooleanPreference(SettingsPreferences.ALLERGEN, defaultValue = true)
|
||||
|
||||
/**
|
||||
* This value determines which canteen the user has selected.
|
||||
*/
|
||||
var preferenceColour: Boolean
|
||||
get() = repo.getBooleanPreference(SettingsPreferences.COLOUR_PREFS)
|
||||
set(newValue) { repo.setPreference(SettingsPreferences.COLOUR_PREFS, newValue) }
|
||||
}
|
||||
@@ -2,6 +2,8 @@ package com.denizk0461.studip.viewmodel
|
||||
|
||||
import android.app.Application
|
||||
import com.denizk0461.studip.data.StudIPParser
|
||||
import java.io.IOException
|
||||
import kotlin.jvm.Throws
|
||||
|
||||
/**
|
||||
* View model for [com.denizk0461.studip.activity.FetcherActivity]
|
||||
@@ -10,7 +12,20 @@ import com.denizk0461.studip.data.StudIPParser
|
||||
*/
|
||||
class FetcherViewModel(app: Application) : AppViewModel(app) {
|
||||
|
||||
/**
|
||||
* Instance of the parser used to fetch and parse the user's Stud.IP schedule.
|
||||
*/
|
||||
private val parser: StudIPParser = StudIPParser(app)
|
||||
|
||||
fun parse(html: String) { doAsync { parser.parse(html, {}) }}
|
||||
/**
|
||||
* Fetches and parses the user's Stud.IP schedule.
|
||||
*
|
||||
* @param html source code of the schedule website
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
fun parse(html: String) {
|
||||
doAsync {
|
||||
parser.parse(html)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,6 +70,13 @@ class SettingsViewModel(app: Application) : AppViewModel(app) {
|
||||
get() = repo.getBooleanPreference(SettingsPreferences.LAUNCH_CANTEEN_ON_START)
|
||||
set(newValue) { repo.setPreference(SettingsPreferences.LAUNCH_CANTEEN_ON_START, newValue) }
|
||||
|
||||
/**
|
||||
* This value determines which canteen the user has selected.
|
||||
*/
|
||||
var preferenceColour: Boolean
|
||||
get() = repo.getBooleanPreference(SettingsPreferences.COLOUR_PREFS)
|
||||
set(newValue) { repo.setPreference(SettingsPreferences.COLOUR_PREFS, newValue) }
|
||||
|
||||
/**
|
||||
* This value determines whether the user opts into submitting crash reports.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user