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.WebView
import android.webkit.WebViewClient
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.ViewModelProvider
import com.denizk0461.studip.R
import com.denizk0461.studip.data.showErrorSnackBar
import com.denizk0461.studip.data.showToast
import com.denizk0461.studip.databinding.ActivityFetcherBinding
import com.denizk0461.studip.sheet.TextSheet
import com.denizk0461.studip.viewmodel.FetcherViewModel
import java.io.IOException
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
* profile. Launched through a button in the app's settings.
*/
class FetcherActivity : AppCompatActivity() {
class FetcherActivity : FragmentActivity() {
// View binding
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
* not logged in.
*/
binding.webview.loadUrl("https://elearning.uni-bremen.de/index.php?again=yes")
binding.fab.setOnClickListener { view ->
binding.fab.setOnClickListener {
if (binding.webview.url?.contains(
"https://elearning.uni-bremen.de/dispatch.php/calendar/schedule"
@@ -78,20 +106,19 @@ class FetcherActivity : AppCompatActivity() {
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()
showToast(this, getString(R.string.toast_fetch_finished))
// 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))
theme.showErrorSnackBar(binding.rootView, getString(R.string.fetch_error_snack), binding.bottomAppBar)
}
}
} 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.Parcelable
import android.util.TypedValue
import android.view.View
import android.widget.Toast
import androidx.annotation.AttrRes
import androidx.coordinatorlayout.widget.CoordinatorLayout
@@ -51,13 +52,15 @@ fun showToast(context: Context, text: String) {
Toast.makeText(context, text, Toast.LENGTH_SHORT).show()
}
fun Resources.Theme.showErrorSnackBar(view: CoordinatorLayout, text: String) {
Snackbar
fun Resources.Theme.showErrorSnackBar(view: CoordinatorLayout, text: String, anchor: View? = null) {
val s = Snackbar
.make(view, text, Snackbar.LENGTH_SHORT)
// Set colours to signify an error
.setBackgroundTint(getThemedColor(R.attr.colorErrorContainer))
.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"
// 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.
@@ -7,20 +7,18 @@ import android.os.Bundle
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 isEventStart whether the timestamp to be edited is the start of the event
*/
class TimePickerFragment(
private val timestamp: String,
private val listener: OnTimeSetListener,
private val isEventStart: Boolean,
) : DialogFragment(), TimePickerDialog.OnTimeSetListener {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val timestampSplit = timestamp.split(":")
val timestampSplit = (arguments?.getString("timestamp") ?: "").split(":")
return TimePickerDialog(
activity,
this,
@@ -121,7 +121,7 @@ class CanteenFragment : AppFragment() {
chip.isChecked = getPreference(pref)
// Set a listener for when the user clicks the chip
chip.setOnCheckedChangeListener { buttonView, newValue ->
chip.setOnCheckedChangeListener { _, newValue ->
// Save the preference change to persistent storage
setPreference(pref, newValue)
}
@@ -141,7 +141,6 @@ class SettingsFragment : AppFragment() {
intent.putExtras(bundle)
})
}
10 -> showToast(context, getString(R.string.settings_app_version_10))
15 -> showToast(context, getString(R.string.settings_app_version_15))
20 -> showToast(context, getString(R.string.settings_app_version_20))
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))
222 -> {
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
@@ -1,65 +1,38 @@
package com.denizk0461.studip.model
// TODO KDoc
enum 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,
;
class AllergenPreferences {
/**
* 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.
*
* @param hasWheat
* @param hasRye
* @param hasBarley
* @param hasOats
* @param hasSpelt
* @param hasKamut
* @param hasCrustaceans
* @param hasEggs
* @param hasFish
* @param hasPeanuts
* @param hasSoy
* @param hasDairy
* @param hasAlmonds
* @param hasHazelnuts
* @param hasWalnuts
* @param hasCashewNuts
* @param hasPecans
* @param hasBrazilNuts
* @param hasPistachios
* @param hasMacadamia
* @param hasCelery
* @param hasMustard
* @param hasSulphides
* @param hasLupins
* @param hasSesame
* @param hasMolluscs
* @param hasWheat whether the offer contains gluten through wheat
* @param hasRye whether the offer contains gluten through rye
* @param hasBarley whether the offer contains gluten through barley
* @param hasOats whether the offer contains gluten through oats
* @param hasSpelt whether the offer contains gluten through spelt
* @param hasKamut whether the offer contains gluten through kamut
* @param hasCrustaceans whether the offer contains crustaceans
* @param hasEggs whether the offer contains eggs
* @param hasFish whether the offer contains fish
* @param hasPeanuts whether the offer contains peanuts
* @param hasSoy whether the offer contains soy
* @param hasDairy whether the offer contains dairy
* @param hasAlmonds whether the offer contains almonds
* @param hasHazelnuts whether the offer contains hazelnuts
* @param hasWalnuts whether the offer contains walnuts
* @param hasCashewNuts whether the offer contains cashew nuts
* @param hasPecans whether the offer contains pecan nuts
* @param hasBrazilNuts whether the offer contains brazil nuts
* @param hasPistachios whether the offer contains pistachios
* @param hasMacadamia whether the offer contains macadamia nuts
* @param hasCelery whether the offer contains celery
* @param hasMustard whether the offer contains mustard
* @param hasSulphides whether the offer contains highly concentrated sulphides
* @param hasLupins whether the offer contains lupins
* @param hasSesame whether the offer contains sesame
* @param hasMolluscs whether the offer contains molluscs
*/
data class Object(
val hasWheat: Boolean,
@@ -130,15 +103,16 @@ enum class AllergenPreferences {
}
companion object {
const val C_TRUE: Char = 'y'
const val C_FALSE: Char = 'n'
/**
* Template string representing no allergens.
*/
const val TEMPLATE: 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
*/
fun construct(input: String): Object {
@@ -172,37 +146,5 @@ enum class AllergenPreferences {
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 {
/**
* 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'
/**
* 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 = '.'
/**
* Default value for preferences if none are set.
*/
const val TEMPLATE_EMPTY: String = ".........."
/**
* Object denoting that no dietary preferences apply.
*/
val NONE_MET: Object = construct(TEMPLATE_EMPTY)
/**
@@ -37,6 +37,9 @@ class ScheduleUpdateSheet : AppSheet(R.layout.sheet_schedule_update), TimePicker
// View model
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?) {
super.onViewCreated(view, savedInstanceState)
@@ -54,6 +57,7 @@ class ScheduleUpdateSheet : AppSheet(R.layout.sheet_schedule_update), TimePicker
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
@@ -62,63 +66,63 @@ class ScheduleUpdateSheet : AppSheet(R.layout.sheet_schedule_update), TimePicker
*
* 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)
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
// 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
}
binding.buttonAcademicQuarter.visibility = getVisibilityForAcademicQuarter(
timeslotsAcademicQuarter.contains(timeslotStart),
timeslotsAcademicQuarter.contains(timeslotEnd),
)
// 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")
).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.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")
).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
binding.buttonDelete.setOnClickListener {
viewModel.delete(event)
// Dismiss the sheet upon deletion
dismiss()
/*
* Check if the user has already clicked the button before; this is done to prevent
* 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
@@ -193,6 +208,26 @@ class ScheduleUpdateSheet : AppSheet(R.layout.sheet_schedule_update), TimePicker
timeslotEnd = 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: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"/>
+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: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"/>
+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: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"/>
+15 -9
View File
@@ -8,19 +8,25 @@
<WebView
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
android:layout_height="match_parent"/>
<com.google.android.material.bottomappbar.BottomAppBar
style="@style/Widget.Material3.BottomAppBar"
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
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="@dimen/fab_margin"
android:layout_marginBottom="@dimen/fab_margin"
app:srcCompat="@drawable/save"/>
app:srcCompat="@drawable/save"
android:contentDescription="@string/fetch_bar_save_desc"
app:layout_anchor="@id/bottom_app_bar"/>
<!-- android:layout_gravity="bottom|end"-->
<!-- android:layout_marginEnd="@dimen/fab_margin"-->
<!-- android:layout_marginBottom="@dimen/fab_margin"-->
</androidx.coordinatorlayout.widget.CoordinatorLayout>
@@ -165,11 +165,13 @@
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/button_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="end"
android:layout_marginBottom="8dp">
android:layout_marginBottom="8dp"
android:animateLayoutChanges="true">
<com.google.android.material.button.MaterialButton
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="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="delete">Löschen</string>
<string name="sheet_schedule_update_delete_confirm">Sicher?</string>
<string name="cancel">Abbrechen</string>
<string name="confirm">Bestätigen</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_10">oh jaa</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>
+21 -3
View File
@@ -23,8 +23,19 @@
<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="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="delete">Delete</string>
<string name="sheet_schedule_update_delete_confirm">Sure?</string>
<string name="cancel">Cancel</string>
<string name="confirm">Confirm</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_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_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_10">ahhh yes</string>
<string name="settings_app_version_15">keep clicking</string>
<string name="settings_app_version_20" 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="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>