Converted all bottom sheets to use blank constructors and instead rely on bundles to retrieve data/objects, or custom view models to execute functions

This commit is contained in:
denizk0461
2023-05-02 15:02:47 +02:00
parent 5a9ab267aa
commit 2f4b205703
23 changed files with 491 additions and 314 deletions
@@ -2,12 +2,17 @@ package com.denizk0461.studip.data
import android.content.Context
import android.content.res.Resources
import android.os.Build
import android.os.Bundle
import android.os.Parcelable
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.denizk0461.studip.exception.ParcelNotFoundException
import com.denizk0461.studip.sheet.TextSheet
import com.google.android.material.snackbar.Snackbar
import kotlin.jvm.Throws
@@ -54,6 +59,41 @@ fun Resources.Theme.showErrorSnackBar(view: CoordinatorLayout, text: String) {
.show()
}
/**
* Retrieve a [TextSheet] with text content added as arguments bundle.
*
* @param header header of the sheet
* @param content text content of the sheet
* @return sheet with text added
*/
fun getTextSheet(header: String, content: String): TextSheet = TextSheet().also { sheet ->
val bundle = Bundle()
bundle.putString("header", header)
bundle.putString("content", content)
sheet.arguments = bundle
}
/**
* Retrieves a Parcelable object from a given instance of [Bundle].
*
* @param T type of the object to retrieve
* @param key string key to retrieve the object from the [Bundle]
* @throws ParcelNotFoundException if the Parcel couldn't be retrieved
*/
@Throws(ParcelNotFoundException::class)
inline fun <reified T : Parcelable> Bundle?.getParcelableCompat(key: String): T =
/*
* Since the old getParcelable() call is deprecated since API 33 (Tiramisu) and replaced with a
* type-safe version, and no AppCompat version is available as of now, this API level check is
* necessary instead.
*/
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
this?.getParcelable(key, T::class.java)
} else {
@Suppress("DEPRECATION")
this?.getParcelable(key) as? T
} ?: throw ParcelNotFoundException()
/**
* Provides a conversion method between a timestamp ending in a full hour, and one with an academic
* quarter applied.
@@ -130,7 +130,7 @@ class StwParser(application: Application) {
/*
* Retrieve the opening hours for the canteen.
* TODO this formatting sucks
* TODO this formatting sucks and also this doesn't work
*/
var openingHours = ""
@@ -0,0 +1,5 @@
package com.denizk0461.studip.exception
import java.io.IOException
class ParcelNotFoundException : IOException()
@@ -8,10 +8,10 @@ import androidx.appcompat.widget.PopupMenu
import androidx.fragment.app.viewModels
import com.denizk0461.studip.R
import com.denizk0461.studip.adapter.CanteenOfferPageAdapter
import com.denizk0461.studip.data.getTextSheet
import com.denizk0461.studip.data.showErrorSnackBar
import com.denizk0461.studip.databinding.FragmentCanteenBinding
import com.denizk0461.studip.model.*
import com.denizk0461.studip.sheet.TextSheet
import com.denizk0461.studip.viewmodel.CanteenViewModel
import com.google.android.material.tabs.TabLayoutMediator
@@ -87,7 +87,7 @@ class CanteenFragment : AppFragment() {
// Set up button to display info (most likely opening hours)
binding.buttonInfo.setOnClickListener {
openBottomSheet(
TextSheet(
getTextSheet(
getString(
R.string.canteen_opening_hours,
getCurrentlySelectedCanteenName()
@@ -230,7 +230,15 @@ class CanteenPageFragment : AppFragment(), CanteenOfferItemAdapter.OnClickListen
* @param offer item that has been clicked
*/
override fun onClick(offer: CanteenOfferGroupElement, category: String) {
openBottomSheet(AllergenSheet(offer, category, viewModel.preferenceColour))
openBottomSheet(
AllergenSheet().also { sheet ->
val bundle = Bundle()
bundle.putParcelable("offer", offer)
bundle.putString("category", category)
bundle.putBoolean("preferenceColour", viewModel.preferenceColour)
sheet.arguments = bundle
}
)
}
/**
@@ -74,7 +74,6 @@ class EventPageFragment : AppFragment(), StudIPEventItemAdapter.OnClickListener
// Set up LiveData observer to refresh the view on update
viewModel.getEventsForDay(currentDay).observe(viewLifecycleOwner) { events ->
eventAdapter.setNewData(events)
}
}
@@ -85,11 +84,13 @@ class EventPageFragment : AppFragment(), StudIPEventItemAdapter.OnClickListener
override fun onLongClick(event: StudIPEvent): Boolean {
// Open a bottom sheet to edit the event
openBottomSheet(ScheduleUpdateSheet(event, onUpdate = { eventToUpdate ->
viewModel.update(eventToUpdate)
}, onDelete = { eventToDelete ->
viewModel.delete(eventToDelete)
}))
openBottomSheet(
ScheduleUpdateSheet().also { sheet ->
val bundle = Bundle()
bundle.putParcelable("event", event)
sheet.arguments = bundle
}
)
return true
}
}
@@ -12,12 +12,11 @@ import com.denizk0461.studip.BuildConfig
import com.denizk0461.studip.R
import com.denizk0461.studip.activity.FetcherActivity
import com.denizk0461.studip.activity.ImageActivity
import com.denizk0461.studip.data.getTextSheet
import com.denizk0461.studip.data.showToast
import com.denizk0461.studip.databinding.FragmentSettingsBinding
import com.denizk0461.studip.model.AllergenPreferences
import com.denizk0461.studip.sheet.AllergenConfigSheet
import com.denizk0461.studip.sheet.DevCodeSheet
import com.denizk0461.studip.sheet.TextSheet
import com.denizk0461.studip.viewmodel.SettingsViewModel
import java.text.SimpleDateFormat
import java.util.*
@@ -67,13 +66,10 @@ class SettingsFragment : AppFragment() {
}
}
// Set up button to set allergens
binding.buttonAllergensConfig.setOnClickListener {
openBottomSheet(
AllergenConfigSheet(
AllergenPreferences.construct(viewModel.preferenceAllergenConfig)
) { obj ->
viewModel.preferenceAllergenConfig = obj.deconstruct()
}
AllergenConfigSheet()
)
}
@@ -111,16 +107,18 @@ class SettingsFragment : AppFragment() {
// Set click listener for the data handling dialogue
binding.buttonDataHandling.setOnClickListener {
openBottomSheet(TextSheet(
openBottomSheet(
getTextSheet(
getString(R.string.settings_data_sheet_header),
getString(R.string.settings_data_sheet_content),
))
)
)
}
// Set click listener for showing licences dialogue
binding.buttonLicences.setOnClickListener {
openBottomSheet(
TextSheet(
getTextSheet(
getString(R.string.sheet_licences_header),
getString(R.string.sheet_licences_content),
)
@@ -129,14 +127,7 @@ class SettingsFragment : AppFragment() {
// Set long click listener for licences button to open dev code sheet
binding.buttonLicences.setOnLongClickListener {
openBottomSheet(DevCodeSheet(
viewModel::nukeEvents,
viewModel::nukeOfferItems,
viewModel::nukeOfferCategories,
viewModel::nukeOfferCanteens,
viewModel::nukeOfferDates,
viewModel::nukeEverything,
))
openBottomSheet(DevCodeSheet())
true
}
@@ -1,5 +1,8 @@
package com.denizk0461.studip.model
import android.os.Parcel
import android.os.Parcelable
/**
* Stripped version of CanteenOffer.kt that only carries essential info. Assumes that instances of
* this are assigned to corresponding date, canteen, and category via an instance of
@@ -12,8 +15,39 @@ package com.denizk0461.studip.model
* @param allergens allergens and additives that are present in the item
*/
data class CanteenOfferGroupElement(
// parcel
val title: String,
val price: String,
val dietaryPreferences: String,
val allergens: String,
)
) : Parcelable {
// Generated Parcelable implementation
constructor(source: Parcel) : this(
source.readString() ?: "",
source.readString() ?: "",
source.readString() ?: "",
source.readString() ?: "",
)
override fun describeContents(): Int = 0
override fun writeToParcel(dest: Parcel, flags: Int): Unit = with(dest) {
writeString(title)
writeString(price)
writeString(dietaryPreferences)
writeString(allergens)
}
companion object {
@JvmField
val CREATOR: Parcelable.Creator<CanteenOfferGroupElement> =
object : Parcelable.Creator<CanteenOfferGroupElement> {
override fun createFromParcel(source: Parcel): CanteenOfferGroupElement =
CanteenOfferGroupElement(source)
override fun newArray(size: Int): Array<CanteenOfferGroupElement?> =
arrayOfNulls(size)
}
}
}
@@ -1,5 +1,7 @@
package com.denizk0461.studip.model
import android.os.Parcel
import android.os.Parcelable
import androidx.room.Entity
import androidx.room.PrimaryKey
@@ -27,8 +29,7 @@ data class StudIPEvent(
val timeslotEnd: String,
val timeslotId: Int,
val colour: Int = 0,
) {
) : Parcelable {
/**
* Parse timeslotStart and timeslotEnd into an easily readable string in the following format:
* 12:15 13:45
@@ -37,8 +38,6 @@ data class StudIPEvent(
*/
fun timeslot(): String = "$timeslotStart $timeslotEnd"
// Auto-generated methods. Necessary for [AppDiffUtilCallback]
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
@@ -70,4 +69,38 @@ data class StudIPEvent(
result = 31 * result + colour
return result
}
constructor(source: Parcel) : this(
source.readInt(),
source.readString() ?: "",
source.readString() ?: "",
source.readString() ?: "",
source.readInt(),
source.readString() ?: "",
source.readString() ?: "",
source.readInt(),
source.readInt(),
)
override fun describeContents(): Int = 0
override fun writeToParcel(dest: Parcel, flags: Int): Unit = with(dest) {
writeInt(eventId)
writeString(title)
writeString(lecturer)
writeString(room)
writeInt(day)
writeString(timeslotStart)
writeString(timeslotEnd)
writeInt(timeslotId)
writeInt(colour)
}
companion object {
@JvmField
val CREATOR: Parcelable.Creator<StudIPEvent> = object : Parcelable.Creator<StudIPEvent> {
override fun createFromParcel(source: Parcel): StudIPEvent = StudIPEvent(source)
override fun newArray(size: Int): Array<StudIPEvent?> = arrayOfNulls(size)
}
}
}
@@ -2,30 +2,34 @@ package com.denizk0461.studip.sheet
import android.os.Bundle
import android.view.View
import androidx.fragment.app.viewModels
import com.denizk0461.studip.R
import com.denizk0461.studip.data.showToast
import com.denizk0461.studip.data.viewBinding
import com.denizk0461.studip.databinding.SheetAllergenConfigBinding
import com.denizk0461.studip.model.AllergenPreferences
import com.denizk0461.studip.viewmodel.AllergenConfigViewModel
/**
* Configuration sheet for the user to pick substances they are allergic against. Offers containing
* these allergens will be hidden.
*
* @param currentAllergenConfig currently set allergen preference
* @param onUpdate action to execute to store the allergen preferences
*/
class AllergenConfigSheet(
private val currentAllergenConfig: AllergenPreferences.Object,
private val onUpdate: (obj: AllergenPreferences.Object) -> Unit,
) : AppSheet(R.layout.sheet_allergen_config) {
class AllergenConfigSheet : AppSheet(R.layout.sheet_allergen_config) {
// View binding
private val binding: SheetAllergenConfigBinding by viewBinding(SheetAllergenConfigBinding::bind)
// View model
private val viewModel: AllergenConfigViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Retrieve allergens as currently set by the user from view model
val currentAllergenConfig = AllergenPreferences.construct(
viewModel.preferenceAllergenConfig
)
// Set all check boxes to the currently set values
binding.apply {
checkWheat.isChecked = currentAllergenConfig.hasWheat
@@ -69,36 +73,34 @@ class AllergenConfigSheet(
binding.apply {
// Save the new preferences
onUpdate(
AllergenPreferences.Object(
hasWheat = checkWheat.isChecked,
hasRye = checkRye.isChecked,
hasBarley = checkBarley.isChecked,
hasOats = checkOat.isChecked,
hasSpelt = checkSpelt.isChecked,
hasKamut = checkKamut.isChecked,
hasCrustaceans = checkCrustaceans.isChecked,
hasEggs = checkEggs.isChecked,
hasFish = checkFish.isChecked,
hasPeanuts = checkPeanuts.isChecked,
hasSoy = checkSoy.isChecked,
hasDairy = checkDairy.isChecked,
hasAlmonds = checkAlmonds.isChecked,
hasHazelnuts = checkHazelnuts.isChecked,
hasWalnuts = checkWalnuts.isChecked,
hasCashewNuts = checkCashewNuts.isChecked,
hasPecans = checkPecans.isChecked,
hasBrazilNuts = checkBrazilNuts.isChecked,
hasPistachios = checkPistachios.isChecked,
hasMacadamia = checkMacadamia.isChecked,
hasCelery = checkCelery.isChecked,
hasMustard = checkMustard.isChecked,
hasSulphides = checkSulphides.isChecked,
hasLupins = checkLupins.isChecked,
hasSesame = checkSesame.isChecked,
hasMolluscs = checkMolluscs.isChecked,
)
)
viewModel.preferenceAllergenConfig = AllergenPreferences.Object(
hasWheat = checkWheat.isChecked,
hasRye = checkRye.isChecked,
hasBarley = checkBarley.isChecked,
hasOats = checkOat.isChecked,
hasSpelt = checkSpelt.isChecked,
hasKamut = checkKamut.isChecked,
hasCrustaceans = checkCrustaceans.isChecked,
hasEggs = checkEggs.isChecked,
hasFish = checkFish.isChecked,
hasPeanuts = checkPeanuts.isChecked,
hasSoy = checkSoy.isChecked,
hasDairy = checkDairy.isChecked,
hasAlmonds = checkAlmonds.isChecked,
hasHazelnuts = checkHazelnuts.isChecked,
hasWalnuts = checkWalnuts.isChecked,
hasCashewNuts = checkCashewNuts.isChecked,
hasPecans = checkPecans.isChecked,
hasBrazilNuts = checkBrazilNuts.isChecked,
hasPistachios = checkPistachios.isChecked,
hasMacadamia = checkMacadamia.isChecked,
hasCelery = checkCelery.isChecked,
hasMustard = checkMustard.isChecked,
hasSulphides = checkSulphides.isChecked,
hasLupins = checkLupins.isChecked,
hasSesame = checkSesame.isChecked,
hasMolluscs = checkMolluscs.isChecked,
).deconstruct()
}
// Tell the user that saving was successful
@@ -6,10 +6,13 @@ 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.getParcelableCompat
import com.denizk0461.studip.data.getThemedColor
import com.denizk0461.studip.data.showToast
import com.denizk0461.studip.data.viewBinding
import com.denizk0461.studip.databinding.ItemSheetPreferenceBinding
import com.denizk0461.studip.databinding.SheetAllergenBinding
import com.denizk0461.studip.exception.ParcelNotFoundException
import com.denizk0461.studip.model.Allergens
import com.denizk0461.studip.model.CanteenOfferGroupElement
import com.denizk0461.studip.model.DietaryPreferences
@@ -17,16 +20,8 @@ import com.denizk0461.studip.model.DietaryPreferences
/**
* This class is used to display further information on a canteen offer to the user. Unlike the name
* implies, it displays more than just allergens.
*
* @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) {
class AllergenSheet : AppSheet(R.layout.sheet_allergen) {
// View binding
private val binding: SheetAllergenBinding by viewBinding(SheetAllergenBinding::bind)
@@ -34,60 +29,78 @@ class AllergenSheet(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.apply {
try {
// Retrieve offer as Parcelable from arguments bundle
val offer: CanteenOfferGroupElement = arguments.getParcelableCompat("offer")
// Category header
textCategory.text = getString(R.string.sheet_category, category)
// Retrieve category string
val category = arguments?.getString("category") ?: ""
// Offer text
textTitle.text = offer.title
// Retrieve preference on whether the user wants preferences to be coloured
val displayColours = arguments?.getBoolean("preferenceColour") == true
// Iterate through all dietary preferences
offer.dietaryPreferences.toList().forEachIndexed { index, c ->
// If a preference is met, inflate a view to display it
if (c == 't') {
// Inflate the view
val prefLine = ItemSheetPreferenceBinding.inflate(LayoutInflater.from(context))
binding.apply {
val (stringId, drawableId, colourId) = DietaryPreferences.getData(index)
// Category header
textCategory.text = getString(R.string.sheet_category, category)
// Add the localised text for the preference
prefLine.preferenceText.text = getString(stringId)
// Offer text
textTitle.text = offer.title
// Set the appropriate icon
prefLine.preferenceImage.setImageDrawable(
getDrawable(
context,
drawableId,
// Iterate through all dietary preferences
offer.dietaryPreferences.toList().forEachIndexed { index, c ->
// If a preference is met, inflate a view to display it
if (c == 't') {
// Inflate the view
val prefLine =
ItemSheetPreferenceBinding.inflate(LayoutInflater.from(context))
val (stringId, drawableId, colourId) = DietaryPreferences.getData(index)
// Add the localised text for the preference
prefLine.preferenceText.text = getString(stringId)
// Set the appropriate icon
prefLine.preferenceImage.setImageDrawable(
getDrawable(
context,
drawableId,
)
)
)
// 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
}
// 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)
// Add the view to the sheet's view container
containerPreferences.addView(prefLine.root)
}
}
// Allergen and additive notice
textContent.text = offer.allergens.compileAllergenString()
// Show a price, if one is available
if (offer.price.isBlank()) {
textPrice.visibility = View.GONE
} else {
textPrice.visibility = View.VISIBLE
textPrice.text = offer.price
}
}
} catch (e: ParcelNotFoundException) {
// Tell the user that something went wrong when retrieving Parcelable
showToast(context, getString(R.string.canteen_page_allergen_sheet_error))
// Allergen and additive notice
textContent.text = offer.allergens.compileAllergenString()
// Show a price, if one is available
if (offer.price.isBlank()) {
textPrice.visibility = View.GONE
} else {
textPrice.visibility = View.VISIBLE
textPrice.text = offer.price
}
// Close the sheet
dismiss()
}
}
@@ -5,36 +5,27 @@ import android.net.Uri
import android.os.Bundle
import android.util.Base64
import android.view.View
import androidx.fragment.app.viewModels
import com.denizk0461.studip.R
import com.denizk0461.studip.activity.ImageActivity
import com.denizk0461.studip.data.showToast
import com.denizk0461.studip.data.viewBinding
import com.denizk0461.studip.databinding.SheetDevCodeBinding
import com.denizk0461.studip.viewmodel.DevCodeViewModel
import java.nio.charset.StandardCharsets
/**
* Sheet that presents the user / developer with a text field to input developer codes / cheat
* codes.
*
* @param nukeEvents deletes all events
* @param nukeOfferItems deletes all canteen items
* @param nukeOfferCategories deletes all canteen categories
* @param nukeOfferCanteens deletes all canteens
* @param nukeOfferDates deletes all canteen dates
* @param nukeEverything deletes everything
*/
class DevCodeSheet(
private val nukeEvents: () -> Unit,
private val nukeOfferItems: () -> Unit,
private val nukeOfferCategories: () -> Unit,
private val nukeOfferCanteens: () -> Unit,
private val nukeOfferDates: () -> Unit,
private val nukeEverything: () -> Unit,
) : AppSheet(R.layout.sheet_dev_code) {
class DevCodeSheet : AppSheet(R.layout.sheet_dev_code) {
// View binding
private val binding: SheetDevCodeBinding by viewBinding(SheetDevCodeBinding::bind)
// View model
private val viewModel: DevCodeViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@@ -81,27 +72,27 @@ class DevCodeSheet(
})
}
"NEVENT" -> { // nuke events
nukeEvents()
viewModel.nukeEvents()
showToast(context, "Nuked all events")
}
"NCANTE" -> { // nuke canteens
nukeOfferCanteens()
viewModel.nukeOfferCanteens()
showToast(context, "Nuked all canteens")
}
"NDATES" -> { // nuke dates
nukeOfferDates()
viewModel.nukeOfferDates()
showToast(context, "Nuked all dates")
}
"NCATEG" -> { // nuke categories
nukeOfferCategories()
viewModel.nukeOfferCategories()
showToast(context, "Nuked all categories")
}
"NITEMS" -> { // nuke items
nukeOfferItems()
viewModel.nukeOfferItems()
showToast(context, "Nuked all items")
}
"NEVERY" -> { // nuke everything
nukeEverything()
viewModel.nukeEverything()
showToast(context, "Nuked everything")
}
else -> showToast(context, "Code is invalid")
@@ -4,8 +4,10 @@ import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.viewModels
import androidx.transition.TransitionManager
import com.denizk0461.studip.R
import com.denizk0461.studip.data.getParcelableCompat
import com.denizk0461.studip.data.getTimestampAcademicQuarterEnd
import com.denizk0461.studip.data.getTimestampAcademicQuarterStart
import com.denizk0461.studip.data.parseToMinutes
@@ -15,136 +17,152 @@ import com.denizk0461.studip.data.viewBinding
import com.denizk0461.studip.databinding.SheetScheduleUpdateBinding
import com.denizk0461.studip.dialog.TimePickerFragment
import com.denizk0461.studip.exception.AcademicQuarterNotApplicableException
import com.denizk0461.studip.exception.ParcelNotFoundException
import com.denizk0461.studip.model.StudIPEvent
import com.denizk0461.studip.viewmodel.ScheduleUpdateViewModel
/**
* Update sheet to allow the user to edit attributes for a specific schedule event.
*
* @param event event to be updated
* @param onUpdate action to execute to update the event
* @param onDelete action to execute to delete the event
*/
class ScheduleUpdateSheet(
private val event: StudIPEvent,
private val onUpdate: (event: StudIPEvent) -> Unit,
private val onDelete: (event: StudIPEvent) -> Unit,
) : AppSheet(R.layout.sheet_schedule_update), TimePickerFragment.OnTimeSetListener {
class ScheduleUpdateSheet : AppSheet(R.layout.sheet_schedule_update), TimePickerFragment.OnTimeSetListener {
// Editable fields
private var timeslotStart = event.timeslotStart
private var timeslotEnd = event.timeslotEnd
private var colour = event.colour
private var timeslotStart = ""
private var timeslotEnd = ""
private var colour = -1
// View binding
private val binding: SheetScheduleUpdateBinding by viewBinding(SheetScheduleUpdateBinding::bind)
// View model
private val viewModel: ScheduleUpdateViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Set editable text fields
binding.editTextTitle.setText(event.title)
binding.editTextLecturers.setText(event.lecturer)
binding.editTextRoom.setText(event.room)
try {
// Retrieve event from arguments bundle
val event: StudIPEvent = arguments.getParcelableCompat("event")
/*
* Show academic quarter suggestion if it is applicable. For this to be the case, both the
* start timestamp and the end timestamp must match up in a pattern that would make it
* likely for the course to use the academic quarter. Example:
* 14:00 - 16:00 -> likely 14:15 - 15:45
*
* 13:00 - 15:30 -> likely no academic quarter intended
*/
binding.buttonAcademicQuarter.visibility = if (
timeslotsAcademicQuarter.contains(timeslotStart) &&
timeslotsAcademicQuarter.contains(timeslotEnd)
) {
binding.buttonAcademicQuarter.setOnClickListener {
try {
// Retrieve new timestamps with the academic quarter applied, or throw a tantrum
val newStart = getTimestampAcademicQuarterStart(timeslotStart)
val newEnd = getTimestampAcademicQuarterEnd(timeslotEnd)
// Set values of the text field to that of the event
timeslotStart = event.timeslotStart
timeslotEnd = event.timeslotEnd
colour = event.colour
// Set the buttons to the new timesatmps
timeslotStart = newStart
binding.buttonTimeStart.text = newStart
timeslotEnd = newEnd
binding.buttonTimeEnd.text = newEnd
// Set editable text fields
binding.editTextTitle.setText(event.title)
binding.editTextLecturers.setText(event.lecturer)
binding.editTextRoom.setText(event.room)
/*
/*
* Show academic quarter suggestion if it is applicable. For this to be the case, both the
* start timestamp and the end timestamp must match up in a pattern that would make it
* likely for the course to use the academic quarter. Example:
* 14:00 - 16:00 -> likely 14:15 - 15:45
*
* 13:00 - 15:30 -> likely no academic quarter intended
*/
binding.buttonAcademicQuarter.visibility = if (
timeslotsAcademicQuarter.contains(timeslotStart) &&
timeslotsAcademicQuarter.contains(timeslotEnd)
) {
binding.buttonAcademicQuarter.setOnClickListener {
try {
// Retrieve new timestamps with the academic quarter applied, or throw a tantrum
val newStart = getTimestampAcademicQuarterStart(timeslotStart)
val newEnd = getTimestampAcademicQuarterEnd(timeslotEnd)
// Set the buttons to the new timesatmps
timeslotStart = newStart
binding.buttonTimeStart.text = newStart
timeslotEnd = newEnd
binding.buttonTimeEnd.text = newEnd
/*
* Attempt to fix the issue of the bottom sheet jumping up when hiding the
* button.
*/
TransitionManager.beginDelayedTransition(binding.sheet.parent as ViewGroup)
binding.buttonAcademicQuarter.visibility = View.GONE
TransitionManager.beginDelayedTransition(binding.sheet.parent as ViewGroup)
binding.buttonAcademicQuarter.visibility = View.GONE
// Catch if a timeslot couldn't be found in the list, and tell the user
} catch (e: AcademicQuarterNotApplicableException) {
showToast(context, getString(R.string.sheet_schedule_update_hint_quarter_error))
// Catch if a timeslot couldn't be found in the list, and tell the user
} catch (e: AcademicQuarterNotApplicableException) {
showToast(
context,
getString(R.string.sheet_schedule_update_hint_quarter_error)
)
}
}
// If the pattern applies, show the button to the user
View.VISIBLE
} else {
// Hide the button if the action is not necessary or applicable
View.GONE
}
// If the pattern applies, show the button to the user
View.VISIBLE
} else {
// Hide the button if the action is not necessary or applicable
View.GONE
}
// Prepare timestamp buttons
binding.buttonTimeStart.text = timeslotStart
binding.buttonTimeStart.setOnClickListener {
// Launch a time picker dialogue to pick a new start timestamp
TimePickerFragment(
binding.buttonTimeStart.text.toString(),
this,
true,
).show((context as FragmentActivity).supportFragmentManager, "timePicker")
}
binding.buttonTimeEnd.text = timeslotEnd
binding.buttonTimeEnd.setOnClickListener {
// Launch a time picker dialogue to pick a new end timestamp
TimePickerFragment(
binding.buttonTimeEnd.text.toString(),
this,
false,
).show((context as FragmentActivity).supportFragmentManager, "timePicker")
}
// Prepare timestamp buttons
binding.buttonTimeStart.text = timeslotStart
binding.buttonTimeStart.setOnClickListener {
// Launch a time picker dialogue to pick a new start timestamp
TimePickerFragment(
binding.buttonTimeStart.text.toString(),
this,
true,
).show((context as FragmentActivity).supportFragmentManager, "timePicker")
}
binding.buttonTimeEnd.text = timeslotEnd
binding.buttonTimeEnd.setOnClickListener {
// Launch a time picker dialogue to pick a new end timestamp
TimePickerFragment(
binding.buttonTimeEnd.text.toString(),
this,
false,
).show((context as FragmentActivity).supportFragmentManager, "timePicker")
}
/*
* TODO implement option to change the colour for the course - single-selection coloured
* filter buttons with checkmarks when they're selected maybe?
*/
/*
* TODO implement option to change the colour for the course - single-selection coloured
* filter buttons with checkmarks when they're selected maybe?
*/
// Prepare cancel button
binding.buttonCancel.setOnClickListener {
// Do nothing and dismiss the sheet
dismiss()
}
// Prepare cancel button
binding.buttonCancel.setOnClickListener {
// Do nothing and dismiss the sheet
dismiss()
}
// Prepare delete button
binding.buttonDelete.setOnClickListener {
viewModel.delete(event)
// Dismiss the sheet upon deletion
dismiss()
}
// Prepare delete button
binding.buttonDelete.setOnClickListener {
onDelete(event)
// Dismiss the sheet upon deletion
dismiss()
}
// Prepare update/save button
binding.buttonSave.setOnClickListener {
onUpdate(
// construct new StudIPEvent from the data the user may have edited
StudIPEvent(
eventId = event.eventId,
title = binding.editTextTitle.text.toString(),
lecturer = binding.editTextLecturers.text.toString(),
room = binding.editTextRoom.text.toString(),
day = event.day,
timeslotStart = timeslotStart,
timeslotEnd = timeslotEnd,
timeslotId = timeslotStart.parseToMinutes(),
colour = colour,
// Prepare update/save button
binding.buttonSave.setOnClickListener {
viewModel.update(
// construct new StudIPEvent from the data the user may have edited
StudIPEvent(
eventId = event.eventId,
title = binding.editTextTitle.text.toString(),
lecturer = binding.editTextLecturers.text.toString(),
room = binding.editTextRoom.text.toString(),
day = event.day,
timeslotStart = timeslotStart,
timeslotEnd = timeslotEnd,
timeslotId = timeslotStart.parseToMinutes(),
colour = colour,
)
)
)
// Dismiss the sheet upon update
// Dismiss the sheet upon update
dismiss()
}
} catch (e: ParcelNotFoundException) {
// Tell the user that something went wrong when retrieving Parcelable
showToast(context, getString(R.string.event_update_sheet_error))
// Close the sheet
dismiss()
}
}
@@ -8,14 +8,8 @@ import com.denizk0461.studip.databinding.SheetTextBinding
/**
* Bottom sheet used for displaying any sort of text to the user.
*
* @param header headline for the window
* @param content content text for the window
*/
class TextSheet(
private val header: String,
private val content: String,
) : AppSheet(R.layout.sheet_text) {
class TextSheet : AppSheet(R.layout.sheet_text) {
// View binding
private val binding: SheetTextBinding by viewBinding(SheetTextBinding::bind)
@@ -23,6 +17,12 @@ class TextSheet(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Retrieve headline for the window
val header = arguments?.getString("header") ?: ""
// Retrieve content for the window
val content = arguments?.getString("content") ?: ""
// Set text values
binding.apply {
textHeader.text = header
@@ -0,0 +1,17 @@
package com.denizk0461.studip.viewmodel
import android.app.Application
import com.denizk0461.studip.model.AllergenPreferences
import com.denizk0461.studip.model.SettingsPreferences
class AllergenConfigViewModel(application: Application) : AppViewModel(application) {
/**
* This value determines which allergens the user wants to have displayed or hidden.
*/
var preferenceAllergenConfig: String
get() = repo.getStringPreference(
SettingsPreferences.ALLERGEN_CONFIG, defaultValue = AllergenPreferences.TEMPLATE
)
set(newValue) { repo.setPreference(SettingsPreferences.ALLERGEN_CONFIG, newValue) }
}
@@ -0,0 +1,42 @@
package com.denizk0461.studip.viewmodel
import android.app.Application
class DevCodeViewModel(application: Application) : AppViewModel(application) {
/**
* Deletes everything from the database.
*/
fun nukeEverything() {
nukeEvents()
nukeOfferItems()
nukeOfferCategories()
nukeOfferCanteens()
nukeOfferDates()
}
/**
* Deletes all Stud.IP events from the database.
*/
fun nukeEvents() { doAsync { repo.nukeEvents() } }
/**
* Deletes all canteen items from the database.
*/
fun nukeOfferItems() { doAsync { repo.nukeOfferItems() } }
/**
* Deletes all canteen categories from the database.
*/
fun nukeOfferCategories() { doAsync { repo.nukeOfferCategories() } }
/**
* Deletes all canteens from the database.
*/
fun nukeOfferCanteens() { doAsync { repo.nukeOfferCanteens() } }
/**
* Deletes all canteen dates from the database.
*/
fun nukeOfferDates() { doAsync { repo.nukeOfferDates() } }
}
@@ -17,18 +17,4 @@ class EventPageViewModel(app: Application) : AppViewModel(app) {
* @return Stud.IP events for a certain day exposed through a LiveData object
*/
fun getEventsForDay(day: Int): LiveData<List<StudIPEvent>> = repo.getEventsForDay(day)
/**
* Updates a schedule element.
*
* @param event the event to update
*/
fun update(event: StudIPEvent) { doAsync { repo.update(event) } }
/**
* Deletes a schedule element.
*
* @param event the event to delete
*/
fun delete(event: StudIPEvent) { doAsync { repo.delete(event) } }
}
@@ -0,0 +1,21 @@
package com.denizk0461.studip.viewmodel
import android.app.Application
import com.denizk0461.studip.model.StudIPEvent
class ScheduleUpdateViewModel(application: Application) : AppViewModel(application) {
/**
* Updates a schedule element.
*
* @param event the event to update
*/
fun update(event: StudIPEvent) { doAsync { repo.update(event) } }
/**
* Deletes a schedule element.
*
* @param event the event to delete
*/
fun delete(event: StudIPEvent) { doAsync { repo.delete(event) } }
}
@@ -1,7 +1,6 @@
package com.denizk0461.studip.viewmodel
import android.app.Application
import com.denizk0461.studip.model.AllergenPreferences
import com.denizk0461.studip.model.SettingsPreferences
/**
@@ -11,44 +10,6 @@ import com.denizk0461.studip.model.SettingsPreferences
*/
class SettingsViewModel(app: Application) : AppViewModel(app) {
/**
* Deletes everything from the database.
*/
fun nukeEverything() {
nukeEvents()
nukeOfferItems()
nukeOfferCategories()
nukeOfferCanteens()
nukeOfferDates()
}
/**
* Deletes all Stud.IP events from the database.
*/
fun nukeEvents() { doAsync { repo.nukeEvents() } }
/**
* Deletes all canteen items from the database.
*/
fun nukeOfferItems() { doAsync { repo.nukeOfferItems() } }
/**
* Deletes all canteen categories from the database.
*/
fun nukeOfferCategories() { doAsync { repo.nukeOfferCategories() } }
/**
* Deletes all canteens from the database.
*/
fun nukeOfferCanteens() { doAsync { repo.nukeOfferCanteens() } }
/**
* Deletes all canteen dates from the database.
*/
fun nukeOfferDates() { doAsync { repo.nukeOfferDates() } }
/**
* This value determines whether the user wants to have the next course in their schedule
* highlighted.
@@ -57,15 +18,6 @@ class SettingsViewModel(app: Application) : AppViewModel(app) {
get() = repo.getBooleanPreference(SettingsPreferences.COURSE_HIGHLIGHTING)
set(newValue) { repo.setPreference(SettingsPreferences.COURSE_HIGHLIGHTING, newValue) }
/**
* This value determines which allergens the user wants to have displayed or hidden.
*/
var preferenceAllergenConfig: String
get() = repo.getStringPreference(
SettingsPreferences.ALLERGEN_CONFIG, defaultValue = AllergenPreferences.TEMPLATE
)
set(newValue) { repo.setPreference(SettingsPreferences.ALLERGEN_CONFIG, newValue) }
/**
* This value determines whether the user wants to have allergens marked.
*/