FetcherActivity.kt now has a bottom app bar with useful actions and extends FragmentActivity instead of AppCompatActivity to show a tutorial bottom sheet

This commit is contained in:
denizk0461
2023-05-02 22:35:19 +02:00
parent 36a7ae0035
commit a5c66aaaff
17 changed files with 236 additions and 165 deletions
@@ -5,12 +5,13 @@ import android.os.Bundle
import android.webkit.WebResourceRequest import android.webkit.WebResourceRequest
import android.webkit.WebView import android.webkit.WebView
import android.webkit.WebViewClient import android.webkit.WebViewClient
import android.widget.Toast import androidx.fragment.app.FragmentActivity
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import com.denizk0461.studip.R import com.denizk0461.studip.R
import com.denizk0461.studip.data.showErrorSnackBar import com.denizk0461.studip.data.showErrorSnackBar
import com.denizk0461.studip.data.showToast
import com.denizk0461.studip.databinding.ActivityFetcherBinding import com.denizk0461.studip.databinding.ActivityFetcherBinding
import com.denizk0461.studip.sheet.TextSheet
import com.denizk0461.studip.viewmodel.FetcherViewModel import com.denizk0461.studip.viewmodel.FetcherViewModel
import java.io.IOException import java.io.IOException
import java.net.URLDecoder import java.net.URLDecoder
@@ -19,7 +20,7 @@ import java.net.URLDecoder
* Activity for allowing the user to log in to access and fetch their schedule from their Stud.IP * Activity for allowing the user to log in to access and fetch their schedule from their Stud.IP
* profile. Launched through a button in the app's settings. * profile. Launched through a button in the app's settings.
*/ */
class FetcherActivity : AppCompatActivity() { class FetcherActivity : FragmentActivity() {
// View binding // View binding
private lateinit var binding: ActivityFetcherBinding private lateinit var binding: ActivityFetcherBinding
@@ -54,13 +55,40 @@ class FetcherActivity : AppCompatActivity() {
} }
} }
// Set up bottom app bar for various actions
binding.bottomAppBar.setOnMenuItemClickListener { item ->
when (item.itemId) {
R.id.close -> {
// Close the activity
finish()
true
}
R.id.refresh -> {
// Refresh the currently opened page
binding.webview.reload()
true
}
R.id.help -> {
// Present the user with a small tutorial on how to fetch their timetable
TextSheet().also { sheet ->
val bundle = Bundle()
bundle.putString("header", getString(R.string.fetch_bar_help_sheet_title))
bundle.putString("content", getString(R.string.fetch_bar_help_sheet_content))
sheet.arguments = bundle
}.show(supportFragmentManager, TextSheet::class.java.simpleName)
true
}
else -> false
}
}
/* /*
* Attempt to load the user-set Stud.IP front page. Redirects to login page if user is * Attempt to load the user-set Stud.IP front page. Redirects to login page if user is
* not logged in. * not logged in.
*/ */
binding.webview.loadUrl("https://elearning.uni-bremen.de/index.php?again=yes") binding.webview.loadUrl("https://elearning.uni-bremen.de/index.php?again=yes")
binding.fab.setOnClickListener { view -> binding.fab.setOnClickListener {
if (binding.webview.url?.contains( if (binding.webview.url?.contains(
"https://elearning.uni-bremen.de/dispatch.php/calendar/schedule" "https://elearning.uni-bremen.de/dispatch.php/calendar/schedule"
@@ -78,20 +106,19 @@ class FetcherActivity : AppCompatActivity() {
viewModel.parse(URLDecoder.decode(p0, "UTF-8")) viewModel.parse(URLDecoder.decode(p0, "UTF-8"))
// Notify the user that the fetch was successful // Notify the user that the fetch was successful
Toast.makeText( showToast(this, getString(R.string.toast_fetch_finished))
this, R.string.toast_fetch_finished, Toast.LENGTH_SHORT
).show()
// Close the activity // Close the activity
finish() finish()
} catch (e: IOException) { } catch (e: IOException) {
// Let the user know that an error occurred // Let the user know that an error occurred
theme.showErrorSnackBar(binding.rootView, getString(R.string.fetch_error_snack)) theme.showErrorSnackBar(binding.rootView, getString(R.string.fetch_error_snack), binding.bottomAppBar)
} }
} }
} else { } else {
theme.showErrorSnackBar(binding.rootView, getString(R.string.fetch_error_webpage_snack)) // Tell the user that they must navigate to their timetable
theme.showErrorSnackBar(binding.rootView, getString(R.string.fetch_error_webpage_snack), binding.bottomAppBar)
} }
} }
} }
@@ -6,6 +6,7 @@ import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import android.util.TypedValue import android.util.TypedValue
import android.view.View
import android.widget.Toast import android.widget.Toast
import androidx.annotation.AttrRes import androidx.annotation.AttrRes
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
@@ -51,13 +52,15 @@ fun showToast(context: Context, text: String) {
Toast.makeText(context, text, Toast.LENGTH_SHORT).show() Toast.makeText(context, text, Toast.LENGTH_SHORT).show()
} }
fun Resources.Theme.showErrorSnackBar(view: CoordinatorLayout, text: String) { fun Resources.Theme.showErrorSnackBar(view: CoordinatorLayout, text: String, anchor: View? = null) {
Snackbar val s = Snackbar
.make(view, text, Snackbar.LENGTH_SHORT) .make(view, text, Snackbar.LENGTH_SHORT)
// Set colours to signify an error // Set colours to signify an error
.setBackgroundTint(getThemedColor(R.attr.colorErrorContainer)) .setBackgroundTint(getThemedColor(R.attr.colorErrorContainer))
.setTextColor(getThemedColor(R.attr.colorOnErrorContainer)) .setTextColor(getThemedColor(R.attr.colorOnErrorContainer))
.show()
anchor?.let { a -> s.setAnchorView(a) }
s.show()
} }
/** /**
@@ -24,7 +24,7 @@ class AppRepository(app: Application) {
private val dietaryPrefString = "prefs_obj" private val dietaryPrefString = "prefs_obj"
// Blank dietary preference regular expression // Blank dietary preference regular expression
private val blankDietaryPrefs: String = DietaryPreferences.C_FALSE.toString().repeat(10) private val blankDietaryPrefs: String = DietaryPreferences.TEMPLATE_EMPTY
/** /**
* Used to observe just to register an update of the user-set dietary preferences. * Used to observe just to register an update of the user-set dietary preferences.
@@ -7,20 +7,18 @@ import android.os.Bundle
import android.widget.TimePicker import android.widget.TimePicker
/** /**
* Dialogue used for letting the user pick a timestamp. * Dialogue used for letting the user pick a timestamp. TODO crashes on config change (multitask)
* *
* @param timestamp timestamp that is to be edited
* @param listener listener for when the user finishes setting a time * @param listener listener for when the user finishes setting a time
* @param isEventStart whether the timestamp to be edited is the start of the event * @param isEventStart whether the timestamp to be edited is the start of the event
*/ */
class TimePickerFragment( class TimePickerFragment(
private val timestamp: String,
private val listener: OnTimeSetListener, private val listener: OnTimeSetListener,
private val isEventStart: Boolean, private val isEventStart: Boolean,
) : DialogFragment(), TimePickerDialog.OnTimeSetListener { ) : DialogFragment(), TimePickerDialog.OnTimeSetListener {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val timestampSplit = timestamp.split(":") val timestampSplit = (arguments?.getString("timestamp") ?: "").split(":")
return TimePickerDialog( return TimePickerDialog(
activity, activity,
this, this,
@@ -121,7 +121,7 @@ class CanteenFragment : AppFragment() {
chip.isChecked = getPreference(pref) chip.isChecked = getPreference(pref)
// Set a listener for when the user clicks the chip // Set a listener for when the user clicks the chip
chip.setOnCheckedChangeListener { buttonView, newValue -> chip.setOnCheckedChangeListener { _, newValue ->
// Save the preference change to persistent storage // Save the preference change to persistent storage
setPreference(pref, newValue) setPreference(pref, newValue)
} }
@@ -141,7 +141,6 @@ class SettingsFragment : AppFragment() {
intent.putExtras(bundle) intent.putExtras(bundle)
}) })
} }
10 -> showToast(context, getString(R.string.settings_app_version_10))
15 -> showToast(context, getString(R.string.settings_app_version_15)) 15 -> showToast(context, getString(R.string.settings_app_version_15))
20 -> showToast(context, getString(R.string.settings_app_version_20)) 20 -> showToast(context, getString(R.string.settings_app_version_20))
30 -> showToast(context, getString(R.string.settings_app_version_30)) 30 -> showToast(context, getString(R.string.settings_app_version_30))
@@ -151,7 +150,10 @@ class SettingsFragment : AppFragment() {
200 -> showToast(context, getString(R.string.settings_app_version_200)) 200 -> showToast(context, getString(R.string.settings_app_version_200))
222 -> { 222 -> {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(mysteryLink))) startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(mysteryLink)))
appVersionClick = -1 }
238 -> { // after 222 has been reached, this loops every 16 clicks
showToast(context, resources.getStringArray(R.array.cheesy).random())
appVersionClick = 222
} }
} }
appVersionClick += 1 appVersionClick += 1
@@ -1,65 +1,38 @@
package com.denizk0461.studip.model package com.denizk0461.studip.model
// TODO KDoc // TODO KDoc
enum class AllergenPreferences { class AllergenPreferences {
GLUTEN_WHEAT,
GLUTEN_RYE,
GLUTEN_BARLEY,
GLUTEN_OATS,
GLUTEN_SPELT,
GLUTEN_KAMUT,
CRUSTACEANS,
EGGS,
FISH,
PEANUTS,
SOY,
DAIRY,
ALMONDS,
HAZELNUTS,
WALNUTS,
CASHEW_NUTS,
PECANS,
BRAZIL_NUTS,
PISTACHIOS,
MACADAMIA,
CELERY,
MUSTARD,
SULPHIDES,
LUPINS,
SESAME,
MOLLUSCS,
;
/** /**
* Object that holds allergy preferences. Can be used to hold both the user-set values as well * Object that holds allergy preferences. Can be used to hold both the user-set values as well
* as the values of an individual canteen item. * as the values of an individual canteen item.
* *
* @param hasWheat * @param hasWheat whether the offer contains gluten through wheat
* @param hasRye * @param hasRye whether the offer contains gluten through rye
* @param hasBarley * @param hasBarley whether the offer contains gluten through barley
* @param hasOats * @param hasOats whether the offer contains gluten through oats
* @param hasSpelt * @param hasSpelt whether the offer contains gluten through spelt
* @param hasKamut * @param hasKamut whether the offer contains gluten through kamut
* @param hasCrustaceans * @param hasCrustaceans whether the offer contains crustaceans
* @param hasEggs * @param hasEggs whether the offer contains eggs
* @param hasFish * @param hasFish whether the offer contains fish
* @param hasPeanuts * @param hasPeanuts whether the offer contains peanuts
* @param hasSoy * @param hasSoy whether the offer contains soy
* @param hasDairy * @param hasDairy whether the offer contains dairy
* @param hasAlmonds * @param hasAlmonds whether the offer contains almonds
* @param hasHazelnuts * @param hasHazelnuts whether the offer contains hazelnuts
* @param hasWalnuts * @param hasWalnuts whether the offer contains walnuts
* @param hasCashewNuts * @param hasCashewNuts whether the offer contains cashew nuts
* @param hasPecans * @param hasPecans whether the offer contains pecan nuts
* @param hasBrazilNuts * @param hasBrazilNuts whether the offer contains brazil nuts
* @param hasPistachios * @param hasPistachios whether the offer contains pistachios
* @param hasMacadamia * @param hasMacadamia whether the offer contains macadamia nuts
* @param hasCelery * @param hasCelery whether the offer contains celery
* @param hasMustard * @param hasMustard whether the offer contains mustard
* @param hasSulphides * @param hasSulphides whether the offer contains highly concentrated sulphides
* @param hasLupins * @param hasLupins whether the offer contains lupins
* @param hasSesame * @param hasSesame whether the offer contains sesame
* @param hasMolluscs * @param hasMolluscs whether the offer contains molluscs
*/ */
data class Object( data class Object(
val hasWheat: Boolean, val hasWheat: Boolean,
@@ -130,15 +103,16 @@ enum class AllergenPreferences {
} }
companion object { companion object {
const val C_TRUE: Char = 'y'
const val C_FALSE: Char = 'n'
/**
* Template string representing no allergens.
*/
const val TEMPLATE: String = "" const val TEMPLATE: String = ""
/** /**
* Constructs an AllergenPreferenceObject from a string. * Constructs an AllergenPreferenceObject from a string.
* *
* @param input string that must be 26 characters long * @param input string that must be 26 characters long. This will NOT be checked
* @return instance of AllergenPreferences.Object with preferences inserted * @return instance of AllergenPreferences.Object with preferences inserted
*/ */
fun construct(input: String): Object { fun construct(input: String): Object {
@@ -172,37 +146,5 @@ enum class AllergenPreferences {
elements.contains("n"), elements.contains("n"),
) )
} }
// String(
// charArrayOf(
// charFor(obj.hasWheat),
// charFor(obj.hasRye),
// charFor(obj.hasBarley),
// charFor(obj.hasOats),
// charFor(obj.hasSpelt),
// charFor(obj.hasKamut),
// charFor(obj.hasCrustaceans),
// charFor(obj.hasEggs),
// charFor(obj.hasFish),
// charFor(obj.hasPeanuts),
// charFor(obj.hasSoy),
// charFor(obj.hasDairy),
// charFor(obj.hasAlmonds),
// charFor(obj.hasHazelnuts),
// charFor(obj.hasWalnuts),
// charFor(obj.hasCashewNuts),
// charFor(obj.hasPecans),
// charFor(obj.hasBrazilNuts),
// charFor(obj.hasPistachios),
// charFor(obj.hasMacadamia),
// charFor(obj.hasCelery),
// charFor(obj.hasMustard),
// charFor(obj.hasSulphides),
// charFor(obj.hasLupins),
// charFor(obj.hasSesame),
// charFor(obj.hasMolluscs),
// )
// )
private fun charFor(pref: Boolean) = if (pref) C_TRUE else C_FALSE
} }
} }
@@ -128,17 +128,23 @@ enum class DietaryPreferences(val value: String) {
companion object { companion object {
/** /**
* Char used to construct a regex to denote that a preference is met * Char used to construct a regex to denote that a preference is met.
*/ */
const val C_TRUE: Char = 't' const val C_TRUE: Char = 't'
/** /**
* Char used to construct a regex to denote that a preference is not met * Char used to construct a regex to denote that a preference is not met.
*/ */
const val C_FALSE: Char = '.' const val C_FALSE: Char = '.'
/**
* Default value for preferences if none are set.
*/
const val TEMPLATE_EMPTY: String = ".........." const val TEMPLATE_EMPTY: String = ".........."
/**
* Object denoting that no dietary preferences apply.
*/
val NONE_MET: Object = construct(TEMPLATE_EMPTY) val NONE_MET: Object = construct(TEMPLATE_EMPTY)
/** /**
@@ -37,6 +37,9 @@ class ScheduleUpdateSheet : AppSheet(R.layout.sheet_schedule_update), TimePicker
// View model // View model
private val viewModel: ScheduleUpdateViewModel by viewModels() private val viewModel: ScheduleUpdateViewModel by viewModels()
// Whether the user has once clicked the delete button; used to prevent an accidental click
private var hasClickedDelete: Boolean = false
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
@@ -54,6 +57,7 @@ class ScheduleUpdateSheet : AppSheet(R.layout.sheet_schedule_update), TimePicker
binding.editTextLecturers.setText(event.lecturer) binding.editTextLecturers.setText(event.lecturer)
binding.editTextRoom.setText(event.room) binding.editTextRoom.setText(event.room)
/* /*
* Show academic quarter suggestion if it is applicable. For this to be the case, both the * 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 * start timestamp and the end timestamp must match up in a pattern that would make it
@@ -62,63 +66,63 @@ class ScheduleUpdateSheet : AppSheet(R.layout.sheet_schedule_update), TimePicker
* *
* 13:00 - 15:30 -> likely no academic quarter intended * 13:00 - 15:30 -> likely no academic quarter intended
*/ */
binding.buttonAcademicQuarter.visibility = if ( binding.buttonAcademicQuarter.setOnClickListener {
timeslotsAcademicQuarter.contains(timeslotStart) && try {
timeslotsAcademicQuarter.contains(timeslotEnd) // Retrieve new timestamps with the academic quarter applied, or throw a tantrum
) { val newStart = getTimestampAcademicQuarterStart(timeslotStart)
binding.buttonAcademicQuarter.setOnClickListener { val newEnd = getTimestampAcademicQuarterEnd(timeslotEnd)
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 // Set the buttons to the new timesatmps
timeslotStart = newStart timeslotStart = newStart
binding.buttonTimeStart.text = newStart binding.buttonTimeStart.text = newStart
timeslotEnd = newEnd timeslotEnd = newEnd
binding.buttonTimeEnd.text = newEnd binding.buttonTimeEnd.text = newEnd
/* /*
* Attempt to fix the issue of the bottom sheet jumping up when hiding the * Attempt to fix the issue of the bottom sheet jumping up when hiding the
* button. * button.
*/ */
TransitionManager.beginDelayedTransition(binding.sheet.parent as ViewGroup) TransitionManager.beginDelayedTransition(binding.sheet.parent as ViewGroup)
binding.buttonAcademicQuarter.visibility = View.GONE binding.buttonAcademicQuarter.visibility = View.GONE
// Catch if a timeslot couldn't be found in the list, and tell the user // Catch if a timeslot couldn't be found in the list, and tell the user
} catch (e: AcademicQuarterNotApplicableException) { } catch (e: AcademicQuarterNotApplicableException) {
showToast( showToast(
context, context,
getString(R.string.sheet_schedule_update_hint_quarter_error) 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
} }
binding.buttonAcademicQuarter.visibility = getVisibilityForAcademicQuarter(
timeslotsAcademicQuarter.contains(timeslotStart),
timeslotsAcademicQuarter.contains(timeslotEnd),
)
// Prepare timestamp buttons // Prepare timestamp buttons
binding.buttonTimeStart.text = timeslotStart binding.buttonTimeStart.text = timeslotStart
binding.buttonTimeStart.setOnClickListener { binding.buttonTimeStart.setOnClickListener {
// Launch a time picker dialogue to pick a new start timestamp // Launch a time picker dialogue to pick a new start timestamp
TimePickerFragment( TimePickerFragment(
binding.buttonTimeStart.text.toString(),
this, this,
true, true,
).show((context as FragmentActivity).supportFragmentManager, "timePicker") ).also { sheet ->
val bundle = Bundle()
bundle.putString("timestamp", binding.buttonTimeStart.text.toString())
sheet.arguments = bundle
}.show((context as FragmentActivity).supportFragmentManager, "timePicker")
} }
binding.buttonTimeEnd.text = timeslotEnd binding.buttonTimeEnd.text = timeslotEnd
binding.buttonTimeEnd.setOnClickListener { binding.buttonTimeEnd.setOnClickListener {
// Launch a time picker dialogue to pick a new end timestamp // Launch a time picker dialogue to pick a new end timestamp
TimePickerFragment( TimePickerFragment(
binding.buttonTimeEnd.text.toString(),
this, this,
false, false,
).show((context as FragmentActivity).supportFragmentManager, "timePicker") ).also { sheet ->
val bundle = Bundle()
bundle.putString("timestamp", binding.buttonTimeEnd.text.toString())
sheet.arguments = bundle
}.show((context as FragmentActivity).supportFragmentManager, "timePicker")
} }
/* /*
@@ -134,9 +138,20 @@ class ScheduleUpdateSheet : AppSheet(R.layout.sheet_schedule_update), TimePicker
// Prepare delete button // Prepare delete button
binding.buttonDelete.setOnClickListener { binding.buttonDelete.setOnClickListener {
viewModel.delete(event) /*
// Dismiss the sheet upon deletion * Check if the user has already clicked the button before; this is done to prevent
dismiss() * accidentally deleting an event.
*/
if (hasClickedDelete) {
viewModel.delete(event)
// Dismiss the sheet upon deletion
dismiss()
} else {
// Update the text to show the user that they clicked the button once
TransitionManager.beginDelayedTransition(binding.buttonContainer as ViewGroup)
binding.buttonDelete.text = getString(R.string.sheet_schedule_update_delete_confirm)
hasClickedDelete = true
}
} }
// Prepare update/save button // Prepare update/save button
@@ -193,6 +208,26 @@ class ScheduleUpdateSheet : AppSheet(R.layout.sheet_schedule_update), TimePicker
timeslotEnd = newTimestamp timeslotEnd = newTimestamp
binding.buttonTimeEnd.text = newTimestamp binding.buttonTimeEnd.text = newTimestamp
} }
// Set appropriate visibility to the academic quarter button
TransitionManager.beginDelayedTransition(binding.sheet.parent as ViewGroup)
binding.buttonAcademicQuarter.visibility = getVisibilityForAcademicQuarter(
timeslotsAcademicQuarter.contains(timeslotStart),
timeslotsAcademicQuarter.contains(timeslotEnd),
)
} }
} }
/**
* Determines which visibility the academic quarter button should have given the timestamps.
*
* @param isStartValid whether the starting timestamp can be converted to an academic quarter
* @param isEndValid whether the ending timestamp can be converted to an academic quarter
*/
private fun getVisibilityForAcademicQuarter(isStartValid: Boolean, isEndValid: Boolean): Int =
if (isStartValid && isEndValid) {
View.VISIBLE
} else {
View.GONE
}
} }
+1 -1
View File
@@ -1,4 +1,4 @@
<vector android:height="24dp" android:tint="#000000" <vector android:height="24dp" android:tint="?attr/colorOnSurfaceVariant"
android:viewportHeight="24" android:viewportWidth="24" android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/> <path android:fillColor="@android:color/white" android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
+1 -1
View File
@@ -1,4 +1,4 @@
<vector android:height="24dp" android:tint="#000000" <vector android:height="24dp" android:tint="?attr/colorOnSurfaceVariant"
android:viewportHeight="24" android:viewportWidth="24" android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-6h2v6zM13,9h-2L11,7h2v2z"/> <path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-6h2v6zM13,9h-2L11,7h2v2z"/>
+1 -1
View File
@@ -1,4 +1,4 @@
<vector android:height="24dp" android:tint="#000000" <vector android:height="24dp" android:tint="?attr/colorOnSurfaceVariant"
android:viewportHeight="24" android:viewportWidth="24" android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z"/> <path android:fillColor="@android:color/white" android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z"/>
+15 -9
View File
@@ -8,19 +8,25 @@
<WebView <WebView
android:id="@+id/webview" android:id="@+id/webview"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"/>
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" <com.google.android.material.bottomappbar.BottomAppBar
app:layout_constraintStart_toStartOf="parent" style="@style/Widget.Material3.BottomAppBar"
app:layout_constraintEnd_toEndOf="parent"/> android:id="@+id/bottom_app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:menu="@menu/menu_fetcher_bar"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton <com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab" android:id="@+id/fab"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="bottom|end" app:srcCompat="@drawable/save"
android:layout_marginEnd="@dimen/fab_margin" android:contentDescription="@string/fetch_bar_save_desc"
android:layout_marginBottom="@dimen/fab_margin" app:layout_anchor="@id/bottom_app_bar"/>
app:srcCompat="@drawable/save"/> <!-- android:layout_gravity="bottom|end"-->
<!-- android:layout_marginEnd="@dimen/fab_margin"-->
<!-- android:layout_marginBottom="@dimen/fab_margin"-->
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>
@@ -165,11 +165,13 @@
</androidx.appcompat.widget.LinearLayoutCompat> </androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat <androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/button_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:gravity="end" android:gravity="end"
android:layout_marginBottom="8dp"> android:layout_marginBottom="8dp"
android:animateLayoutChanges="true">
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/button_delete" android:id="@+id/button_delete"
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/close"
android:icon="@drawable/cross"
android:title="@string/fetch_bar_close_title"
android:contentDescription="@string/fetch_bar_close_desc"
app:showAsAction="ifRoom"/>
<item
android:id="@+id/refresh"
android:icon="@drawable/refresh"
android:title="@string/fetch_bar_refresh_title"
android:contentDescription="@string/fetch_bar_refresh_desc"
app:showAsAction="ifRoom"/>
<item
android:id="@+id/help"
android:icon="@drawable/info"
android:title="@string/fetch_bar_help_title"
android:contentDescription="@string/fetch_bar_help_desc"
app:showAsAction="ifRoom"/>
</menu>
+11 -1
View File
@@ -23,8 +23,19 @@
<string name="fetch_error_webpage_snack">Du musst deinen Stundenplan öffnen!</string> <string name="fetch_error_webpage_snack">Du musst deinen Stundenplan öffnen!</string>
<string name="canteen_fetch_error">Mensaplan konnte nicht heruntergeladen werden!</string> <string name="canteen_fetch_error">Mensaplan konnte nicht heruntergeladen werden!</string>
<string name="fetch_bar_close_title">Schließen</string>
<string name="fetch_bar_close_desc">Schließe die Webseite, ohne etwas zu speichern</string>
<string name="fetch_bar_refresh_title">Aktualisieren</string>
<string name="fetch_bar_refresh_desc">Aktualisiere die Webseite</string>
<string name="fetch_bar_help_title">Hilfe</string>
<string name="fetch_bar_help_desc">Zeige Hilfe</string>
<string name="fetch_bar_help_sheet_title">Wie Du Deinen Stundenplan in der App speichern kannst</string>
<string name="fetch_bar_help_sheet_content"> • Logge dich mit deinem Stud.IP-Login ein\n • Öffne deinen Stundenplan (falls er sich nicht automatisch öffnet)\n • Klicke auf den \'Speichern\'-Knopf rechts in der unteren Ecke\n • Fertig!</string>
<string name="fetch_bar_save_desc">Speichere den Stundenplan in der App ab</string>
<string name="save">Speichern</string> <string name="save">Speichern</string>
<string name="delete">Löschen</string> <string name="delete">Löschen</string>
<string name="sheet_schedule_update_delete_confirm">Sicher?</string>
<string name="cancel">Abbrechen</string> <string name="cancel">Abbrechen</string>
<string name="confirm">Bestätigen</string> <string name="confirm">Bestätigen</string>
<string name="sheet_schedule_update_header">Veranstaltung bearbeiten</string> <string name="sheet_schedule_update_header">Veranstaltung bearbeiten</string>
@@ -164,7 +175,6 @@
<string name="settings_app_version">App-Version</string> <string name="settings_app_version">App-Version</string>
<string name="settings_app_version_10">oh jaa</string>
<string name="settings_app_version_15">klick weiter</string> <string name="settings_app_version_15">klick weiter</string>
<string name="settings_app_version_121">Solltest du nicht für deine Klausuren lernen?</string> <string name="settings_app_version_121">Solltest du nicht für deine Klausuren lernen?</string>
+21 -3
View File
@@ -23,8 +23,19 @@
<string name="fetch_error_webpage_snack">You must navigate to your schedule!</string> <string name="fetch_error_webpage_snack">You must navigate to your schedule!</string>
<string name="canteen_fetch_error">Couldn\'t retrieve the canteen plan!</string> <string name="canteen_fetch_error">Couldn\'t retrieve the canteen plan!</string>
<string name="fetch_bar_close_title">Close</string>
<string name="fetch_bar_close_desc">Close the website without saving anything</string>
<string name="fetch_bar_refresh_title">Refresh</string>
<string name="fetch_bar_refresh_desc">Refresh the page</string>
<string name="fetch_bar_help_title">Help</string>
<string name="fetch_bar_help_desc">Show help</string>
<string name="fetch_bar_help_sheet_title">How to save your timetable in the app</string>
<string name="fetch_bar_help_sheet_content"> • Log in with your Stud.IP login\n • Open your timetable view (in case it doesn\'t open automatically)\n • Click the \'save\' button in the bottom right corner\n • Done!</string>
<string name="fetch_bar_save_desc">Save the timetable in the app</string>
<string name="save">Save</string> <string name="save">Save</string>
<string name="delete">Delete</string> <string name="delete">Delete</string>
<string name="sheet_schedule_update_delete_confirm">Sure?</string>
<string name="cancel">Cancel</string> <string name="cancel">Cancel</string>
<string name="confirm">Confirm</string> <string name="confirm">Confirm</string>
<string name="sheet_schedule_update_header">Edit Event</string> <string name="sheet_schedule_update_header">Edit Event</string>
@@ -153,7 +164,7 @@
<string name="settings_pref_colour_subtitle">This colours the icons similar to how they look the website</string> <!-- TODO this text sucks, DE too --> <string name="settings_pref_colour_subtitle">This colours the icons similar to how they look the website</string> <!-- TODO this text sucks, DE too -->
<string name="settings_launch_canteen_title">Show canteen plan on app launch</string> <string name="settings_launch_canteen_title">Show canteen plan on app launch</string>
<string name="settings_launch_canteen_subtitle">Otherwise your schedule will be shown at launch</string> <string name="settings_launch_canteen_subtitle">Otherwise your schedule will be shown at launch; CURRENTLY BROKEN</string>
<string name="settings_crashlytics_title">Opt-in to sharing crash data</string> <string name="settings_crashlytics_title">Opt-in to sharing crash data</string>
<string name="settings_crashlytics_subtitle">Data will be anonymised</string> <!-- TODO is this true? --> <string name="settings_crashlytics_subtitle">Data will be anonymised</string> <!-- TODO is this true? -->
@@ -170,7 +181,6 @@
<string name="settings_app_version">App Version</string> <string name="settings_app_version">App Version</string>
<string name="settings_app_version_10">ahhh yes</string>
<string name="settings_app_version_15">keep clicking</string> <string name="settings_app_version_15">keep clicking</string>
<string name="settings_app_version_20" translatable="false">^_^</string> <string name="settings_app_version_20" translatable="false">^_^</string>
<string name="settings_app_version_30" translatable="false">^_____^</string> <string name="settings_app_version_30" translatable="false">^_____^</string>
@@ -181,5 +191,13 @@
<string name="with_love">with ♡️ from Bremen-Osterholz\nmade by denizk0461 \\__*</string> <string name="with_love">with ♡️ from Bremen-Osterholz\nmade by denizk0461 \\__*</string>
<string name="de4thSpirit" translatable="false">In our hearts, she lives forevermore</string> <string-array name="cheesy" translatable="false">
<item>in our hearts, she lives forevermore</item>
<item>it\'s only real when it hurts</item>
<item>can\'t go back in time every second is a new one</item>
<item>doctor, i am the great clown Pagliacci</item>
<item>if we only had one more day, what is the last thing you\'d say?</item>
<item>love with your heart, use your head for everything else</item>
<item>if I die, I hope I\'m buried in a forest that is, if any of it\'s left</item>
</string-array>
</resources> </resources>