Added TimetableOverviewActivity.kt and a means to launch via a button through EventFragment.kt; implementation unfinished

This commit is contained in:
denizk0461
2023-05-25 12:32:10 +02:00
parent e52a055cf1
commit 98a3a13623
22 changed files with 313 additions and 42 deletions
+4 -2
View File
@@ -12,8 +12,8 @@ android {
applicationId = "com.denizk0461.weserplaner"
minSdk = 24
targetSdk = 33
versionCode = 7
versionName = "1.0.3"
versionCode = 8
versionName = "1.1.0"
resourceConfigurations += arrayOf("en", "de")
@@ -65,5 +65,7 @@ dependencies {
implementation("com.google.android.material:material:1.9.0")
implementation("com.github.alamkanak:android-week-view:1.2.6")
implementation("org.jsoup:jsoup:1.15.4")
}
@@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 21,
"identityHash": "202a385ce4f79f957d41c8e4a2b8ab22",
"identityHash": "8cc123552f213d71a339b0c3a89da48c",
"entities": [
{
"tableName": "studip_events",
@@ -268,7 +268,7 @@
},
{
"tableName": "event_tasks",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`taskId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `eventId` INTEGER NOT NULL, `dueDate` INTEGER NOT NULL, `notifyDate` INTEGER NOT NULL, `title` TEXT NOT NULL, `notes` TEXT NOT NULL, `room` TEXT NOT NULL, FOREIGN KEY(`eventId`) REFERENCES `studip_events`(`eventId`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`taskId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `eventId` INTEGER NOT NULL, `dueDate` INTEGER NOT NULL, `notifyDate` INTEGER NOT NULL, `title` TEXT NOT NULL, `notes` TEXT NOT NULL, `room` TEXT NOT NULL, `isFinished` INTEGER NOT NULL, FOREIGN KEY(`eventId`) REFERENCES `studip_events`(`eventId`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "taskId",
@@ -311,6 +311,12 @@
"columnName": "room",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isFinished",
"columnName": "isFinished",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
@@ -338,7 +344,7 @@
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '202a385ce4f79f957d41c8e4a2b8ab22')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '8cc123552f213d71a339b0c3a89da48c')"
]
}
}
+8
View File
@@ -41,6 +41,14 @@
android:configChanges="orientation|keyboardHidden|screenSize"
android:theme="@style/Theme.StudIPTimetable">
</activity>
<activity
android:name=".activity.TimetableOverviewActivity"
android:exported="false"
android:configChanges="orientation|keyboardHidden|screenSize"
android:theme="@style/Theme.StudIPTimetable"
android:screenOrientation="landscape">
</activity>
</application>
</manifest>
@@ -0,0 +1,62 @@
package com.denizk0461.weserplaner.activity
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.lifecycle.ViewModelProvider
import com.alamkanak.weekview.WeekViewEvent
import com.denizk0461.weserplaner.databinding.ActivityTimetableOverviewBinding
import com.denizk0461.weserplaner.viewmodel.TimetableOverviewViewModel
import java.util.Calendar
import java.util.Date
/**
* This activity serves the user in displaying a full week overview of their timetable. This can be
* used to quickly share their timetable with other people, in screenshot form, for example, as
* [com.denizk0461.weserplaner.fragment.EventFragment] only displays one day at a time.
*/
class TimetableOverviewActivity : AppCompatActivity() {
// View binding
private lateinit var binding: ActivityTimetableOverviewBinding
// View model
private lateinit var viewModel: TimetableOverviewViewModel
override fun onCreate(savedInstanceState: Bundle?) {
WindowCompat.setDecorFitsSystemWindows(window, false)
super.onCreate(savedInstanceState)
// Instantiate view model
viewModel = ViewModelProvider.AndroidViewModelFactory(application)
.create(TimetableOverviewViewModel::class.java)
// Inflate view binding and bind to this activity
binding = ActivityTimetableOverviewBinding.inflate(layoutInflater)
setContentView(binding.root)
// Hide system UI elements to maximise space for the user's timetable
hideSystemUi()
val a = WeekViewEvent(0, "hi", createCalendarDate(), createCalendarDate(true))
viewModel.getAllEvents().observe(this) { events ->
binding.weekView.weekViewLoader
}
// TODO pay attention to whether content is hidden behind camera cutouts
}
private fun hideSystemUi() {
WindowCompat.setDecorFitsSystemWindows(window, false)
WindowInsetsControllerCompat(window, binding.root).let { controller ->
controller.hide(WindowInsetsCompat.Type.systemBars())
controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}
}
private fun createCalendarDate(a: Boolean = false) = Calendar.getInstance().also {
it.time = Date(if (!a) 1684792800 else 1684800000) }
}
@@ -0,0 +1,24 @@
package com.denizk0461.weserplaner.adapter
import android.content.Context
import android.widget.ArrayAdapter
import android.widget.Filter
import com.denizk0461.weserplaner.R
/**
* Class used to mitigate issues with the ArrayAdapter deleting items.
*
* @param context context reference
* @param items list of texts to display
*/
class DropdownAdapter(context: Context, items: List<String>)
: ArrayAdapter<String>(context, R.layout.item_dropdown, items) {
private val noOpFilter = object : Filter() {
private val noOpResult = FilterResults()
override fun performFiltering(constraint: CharSequence?) = noOpResult
override fun publishResults(constraint: CharSequence?, results: FilterResults?) {}
}
override fun getFilter() = noOpFilter
}
@@ -11,7 +11,7 @@ import com.denizk0461.weserplaner.R
import com.denizk0461.weserplaner.data.AppDiffUtilCallback
import com.denizk0461.weserplaner.data.isInThePast
import com.denizk0461.weserplaner.databinding.ItemTaskBinding
import com.denizk0461.weserplaner.model.EventTask
import com.denizk0461.weserplaner.model.EventTaskExtended
import com.denizk0461.weserplaner.model.FormattedDate
class TaskOverviewAdapter(
@@ -21,7 +21,7 @@ class TaskOverviewAdapter(
/**
* List of all events.
*/
private val tasks: MutableList<EventTask> = mutableListOf()
private val tasks: MutableList<EventTaskExtended> = mutableListOf()
/**
* View holder class for parent class
@@ -58,12 +58,15 @@ class TaskOverviewAdapter(
// view set-up
// Set title text
holder.binding.textTitle.text = currentItem.title
// Set event title text
holder.binding.textEventTitle.text = currentItem.eventTitle
// Set task title text
holder.binding.textTitle.text = currentItem.taskTitle
// Set due date text to formatted date, TODO let user pick the date format
val dueDate = FormattedDate(currentItem.dueDate)
// Set due date text to formatted date, TODO let user pick the date format
holder.binding.textDueDate.text = dueDate.localisedString(context)
if (currentItem.notifyDate == -1L) {
@@ -93,6 +96,10 @@ class TaskOverviewAdapter(
}, notifyDate.dateString, notifyDate.timeString)
}
holder.binding.textLecturer.text = currentItem.lecturer
holder.binding.textRoom.text = currentItem.room
holder.binding.textNotes.text = currentItem.notes
// Set up single click listener
holder.binding.linearLayout.setOnClickListener {
// Prepare layout animation
@@ -121,7 +128,7 @@ class TaskOverviewAdapter(
*
* @param newData new dataset to be displayed
*/
fun setNewData(newData: List<EventTask>) {
fun setNewData(newData: List<EventTaskExtended>) {
// Calculate the difference between the old list and the new list
val diffResult = DiffUtil.calculateDiff(AppDiffUtilCallback(tasks, newData))
@@ -146,6 +153,6 @@ class TaskOverviewAdapter(
* @param task item that has been long-pressed
* @return whether the long press was successful
*/
fun onLongClick(task: EventTask): Boolean
fun onLongClick(task: EventTaskExtended): Boolean
}
}
@@ -13,6 +13,14 @@ interface AppDAO {
/* --- Stud.IP schedule events --- */
/**
* Retrieve all Stud.IP events from the database.
*
* @return all events
*/
@Query("SELECT * FROM studip_events ORDER BY timeslotId, eventId")
fun getAllEvents(): LiveData<List<StudIPEvent>>
/**
* Retrieves the number of Stud.IP events available in the database.
*
@@ -81,7 +89,8 @@ interface AppDAO {
"studip_events.title AS eventTitle, " +
"studip_events.lecturer, " +
"event_tasks.notes, " +
"studip_events.room " +
"studip_events.room, " +
"event_tasks.isFinished " +
"FROM event_tasks " +
"JOIN studip_events ON event_tasks.eventId = studip_events.eventId " +
"ORDER BY taskId"
@@ -102,7 +111,8 @@ interface AppDAO {
"studip_events.title AS eventTitle, " +
"studip_events.lecturer, " +
"event_tasks.notes, " +
"studip_events.room " +
"studip_events.room, " +
"event_tasks.isFinished " +
"FROM event_tasks " +
"JOIN studip_events ON event_tasks.eventId = studip_events.eventId " +
"ORDER BY taskTitle")
@@ -122,7 +132,8 @@ interface AppDAO {
"studip_events.title AS eventTitle, " +
"studip_events.lecturer, " +
"event_tasks.notes, " +
"studip_events.room " +
"studip_events.room, " +
"event_tasks.isFinished " +
"FROM event_tasks " +
"JOIN studip_events ON event_tasks.eventId = studip_events.eventId " +
"ORDER BY dueDate")
@@ -58,6 +58,13 @@ class AppRepository(app: Application) {
}
}
/**
* Retrieve all Stud.IP events from the database.
*
* @return all events
*/
fun getAllEvents(): LiveData<List<StudIPEvent>> = dao.getAllEvents()
/**
* Retrieves the number of Stud.IP events available in the database.
*
@@ -9,6 +9,7 @@ import androidx.fragment.app.viewModels
import androidx.viewpager2.widget.ViewPager2
import com.denizk0461.weserplaner.R
import com.denizk0461.weserplaner.activity.FetcherActivity
import com.denizk0461.weserplaner.activity.TimetableOverviewActivity
import com.denizk0461.weserplaner.adapter.StudIPEventPageAdapter
import com.denizk0461.weserplaner.data.showSnackBar
import com.denizk0461.weserplaner.databinding.FragmentEventBinding
@@ -43,6 +44,11 @@ class EventFragment : AppFragment<FragmentEventBinding>() {
// Get localised weekday names
weekdays = context.resources?.getStringArray(R.array.weekdays) ?: arrayOf()
// Set up button to view timetable overview
binding.buttonViewOverview.setOnClickListener {
startActivity(Intent(context, TimetableOverviewActivity::class.java))
}
// Set up the view pager's adapter
viewPagerAdapter = StudIPEventPageAdapter(
childFragmentManager,
@@ -176,6 +176,23 @@ class SettingsFragment : AppFragment<FragmentSettingsBinding>() {
}
}
// TODO Set up dropdown menu for picking a date format
// val dateAdapter = DropdownAdapter(
// context,
// context.resources.getStringArray(R.array.settings_date_format_items).toList(),
// )
//
// binding.autoCompleteFont.setAdapter(dateAdapter)
// binding.autoCompleteFont.setText(
// dateAdapter.getItem(storage.getTypefaceIndex()).toString(),
// false,
// )
//
// binding.autoCompleteFont.setOnItemClickListener { _, _, position, _ ->
// storage.setTypefaceIndex(position)
// binding.autoCompleteFont.setText(dateAdapter.getItem(position), false)
// }
// Set up switch for colouring dietary preferences
binding.switchPrefColour.apply {
isChecked = viewModel.preferenceColour
@@ -11,7 +11,7 @@ import com.denizk0461.weserplaner.R
import com.denizk0461.weserplaner.adapter.TaskOverviewAdapter
import com.denizk0461.weserplaner.data.showSnackBar
import com.denizk0461.weserplaner.databinding.FragmentTaskOverviewBinding
import com.denizk0461.weserplaner.model.EventTask
import com.denizk0461.weserplaner.model.EventTaskExtended
import com.denizk0461.weserplaner.values.TaskOrder
import com.denizk0461.weserplaner.viewmodel.TaskOverviewViewModel
@@ -91,7 +91,7 @@ class TaskOverviewFragment : AppFragment<FragmentTaskOverviewBinding>(),
}
}
override fun onLongClick(task: EventTask): Boolean {
override fun onLongClick(task: EventTaskExtended): Boolean {
// TODO
return false
}
@@ -15,6 +15,7 @@ import androidx.room.PrimaryKey
* @param title title of the task
* @param notes user-added notes for the task
* @param room room the task may take place in
* @param isFinished whether this task has been marked as finished by the user
*/
@Entity(
tableName = "event_tasks",
@@ -35,4 +36,5 @@ data class EventTask(
val title: String,
val notes: String,
val room: String,
val isFinished: Boolean = false,
)
@@ -14,15 +14,17 @@ package com.denizk0461.weserplaner.model
* @param lecturer lecturer(s) organising the event
* @param notes user-added notes for the task
* @param room room the task may take place in
* @param isFinished whether this task has been marked as finished by the user
*/
data class EventTaskExtended(
val taskId: Int,
val eventId: Int,
val dueDate: Int,
val notifyDate: Int,
val dueDate: Long,
val notifyDate: Long,
val eventTitle: String,
val taskTitle: String,
val lecturer: String,
val notes: String,
val room: String,
val isFinished: Boolean,
)
@@ -1,7 +1,7 @@
package com.denizk0461.weserplaner.viewmodel
import android.app.Application
import com.denizk0461.weserplaner.model.EventTask
import com.denizk0461.weserplaner.model.EventTaskExtended
import com.denizk0461.weserplaner.values.TaskOrder
/**
@@ -18,8 +18,8 @@ class TaskOverviewViewModel(application: Application) : AppViewModel(application
// fun getTasks(order: TaskOrder): LiveData<List<EventTask>> = repo.getTasks(order)
fun getTasks(order: TaskOrder) = listOf( // dummy data
EventTask(0, 0, 1684758756L, -1L, "title1", "user notes", "GW2 B-idk"),
EventTask(1, 0, 1684798756L, -1L, "title2", "user notes", "GW3 C-idk"),
EventTask(2, 0, 1689758756L, 1689958756L, "title3", "user notes", "GW4 D-idk"),
EventTaskExtended(0, 0, 1684758756L, -1L, "event1", "title1", "lecturer", "user notes", "GW2 B-idk", false),
EventTaskExtended(1, 0, 1684798756L, -1L, "event2", "title2", "lecturer", "user notes", "GW3 C-idk", false),
EventTaskExtended(2, 0, 1689758756L, 1689958756L, "event3", "title3", "lecturer", "user notes user notes user notes user notes user notes user notes user notes user notes user notes user notes user notes user notes user notes user notes user notes user notes user notes user notes user notes user notes user notes user notes user notes user notes user notes", "GW4 D-idk", false),
)
}
@@ -0,0 +1,15 @@
package com.denizk0461.weserplaner.viewmodel
import android.app.Application
import androidx.lifecycle.LiveData
import com.denizk0461.weserplaner.model.StudIPEvent
class TimetableOverviewViewModel(application: Application) : AppViewModel(application) {
/**
* Retrieve all Stud.IP events from the database.
*
* @return all events
*/
fun getAllEvents(): LiveData<List<StudIPEvent>> = repo.getAllEvents()
}
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.alamkanak.weekview.WeekView
android:id="@+id/week_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
+44 -10
View File
@@ -13,17 +13,51 @@
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/app_title_bar"
android:layout_width="wrap_content"
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/title_schedule"
android:textSize="28sp"
app:fontFamily="@font/lato_blackitalic"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="8dp"
app:layout_scrollFlags="scroll"/>
android:layout_marginStart="16dp"
android:layout_marginEnd="8dp"
app:layout_scrollFlags="scroll">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/app_title_bar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/title_schedule"
android:textSize="28sp"
android:layout_marginTop="16dp"
android:layout_marginBottom="8dp"
android:layout_marginEnd="4dp"
app:fontFamily="@font/lato_blackitalic"
android:singleLine="true"
android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever"
android:focusable="true"
android:focusableInTouchMode="true"
android:scrollHorizontally="true"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/button_view_overview"/>
<com.google.android.material.button.MaterialButton
style="@style/Widget.Material3.Button.IconButton.Outlined"
android:id="@+id/button_view_overview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="0dp"
app:icon="@drawable/sort"
app:iconPadding="0dp"
app:strokeColor="?attr/colorOutlineVariant"
app:layout_constraintTop_toTopOf="@id/app_title_bar"
app:layout_constraintBottom_toBottomOf="@id/app_title_bar"
app:layout_constraintEnd_toEndOf="parent"
app:iconTint="?attr/colorOnSurfaceVariant"
tools:ignore="UnusedAttribute"
android:tooltipText="@string/schedule_button_overview_hint"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.tabs.TabLayout
android:id="@+id/day_tab_layout"
@@ -18,7 +18,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="8dp">
android:layout_marginEnd="8dp"
app:layout_scrollFlags="scroll">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/app_title_bar"
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
style="?android:attr/spinnerDropDownItemStyle"
android:singleLine="true"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="marquee"/>
+42 -8
View File
@@ -29,11 +29,11 @@
android:layout_height="wrap_content">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/text_title"
android:id="@+id/text_event_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textSize="22sp"
app:fontFamily="@font/lato_bolditalic"
android:textSize="16sp"
app:fontFamily="@font/lato_italic"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/icon_notify_header"
@@ -46,16 +46,22 @@
android:scaleType="centerInside"
android:tint="?attr/colorText"
android:importantForAccessibility="no"
app:layout_constraintTop_toTopOf="@id/text_title"
app:layout_constraintTop_toTopOf="@id/text_event_title"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/text_due_date"
android:layout_width="wrap_content"
android:id="@+id/text_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="16sp"/>
android:textSize="22sp"
app:fontFamily="@font/lato_bolditalic"/>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/text_due_date"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/layout_expanded_container"
@@ -64,7 +70,6 @@
android:orientation="vertical">
<com.google.android.material.divider.MaterialDivider
android:id="@+id/divider"
style="@style/DividerTheme"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -92,6 +97,35 @@
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/text_lecturer"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/text_room"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="4dp">
<View
android:layout_width="1dp"
android:layout_height="fill_parent"
android:layout_marginHorizontal="8dp"
android:background="?attr/colorText"/>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/text_notes"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.appcompat.widget.LinearLayoutCompat>
+3
View File
@@ -48,6 +48,7 @@
<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, indem du in der unteren Leiste auf das rechte Logo klickst, das wie ein Stift und ein Lineal aussieht\n • Klicke auf den \'Speichern\'-Knopf, der unten in der rechten Ecke auftaucht\n • Fertig!</string>
<string name="fetch_bar_save_desc">Speichere den Stundenplan in der App ab</string>
<string name="schedule_button_overview_hint">Siehe gesamten Stundenplan</string>
<string name="schedule_fab_add_text">Neue Veranstaltung</string>
<string name="schedule_no_events">%ss hast du keine Veranstaltungen.</string>
@@ -249,6 +250,8 @@
<string name="settings_launch_canteen_title">Wähle, mit welchem Bildschirm die App startet</string>
<string name="settings_date_format_title">Wähle das Format für Datumsangaben</string>
<string name="settings_pref_colour_title">Färbe Diätpreferenzen</string>
<string name="settings_pref_colour_subtitle">Dies färbt die kleinen Symbole ähnlich wie auf der Webseite</string>
+9
View File
@@ -49,6 +49,7 @@
<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, by clicking the rightmost icon in the bottom bar that looks like a pencil and a ruler\n • Click the \'save\' button that appears in the bottom right corner\n • Done!</string>
<string name="fetch_bar_save_desc">Save the timetable in the app</string>
<string name="schedule_button_overview_hint">View full schedule</string>
<string name="schedule_fab_add_text">New event</string>
<string name="schedule_no_events">You don\'t have any events for %s.</string>
@@ -257,6 +258,14 @@
<string name="settings_launch_canteen_title">Select which screen to start the app with</string>
<string name="settings_date_format_title">Select which format to show dates in</string>
<string-array name="settings_date_format_items" translatable="false">
<item>24.05.2023, 8:45</item>
<item>20230524, 08:45</item>
<item>20230524, 08:45:11.518</item>
<item>05/24/23, 8:45 AM</item>
</string-array>
<!-- unused - use for possible future implementation of Crashlytics -->
<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? -->