User can now add, edit, and delete timetables without crashing (hopefully)

This commit is contained in:
denizk0461
2023-07-31 22:09:04 +02:00
parent 1f05fad84c
commit 453e65ec48
16 changed files with 265 additions and 38 deletions
+1
View File
@@ -7,6 +7,7 @@
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="jbr-17" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
+1 -1
View File
@@ -1,6 +1,6 @@
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
+1 -1
View File
@@ -7,6 +7,7 @@ WeserPlaner is a work-in-progress Android app for students at the University of
- Downloading and creating events, as well as displaying them.
- Downloading canteen offers, filtering them, and viewing details.
- Viewing additional information on the canteen (opening hours, news).
- Storing multiple timetables is supported.
## What's broken
Currently, nothing is outright broken, but a few things may be bodged together badly.
@@ -15,7 +16,6 @@ Currently, nothing is outright broken, but a few things may be bodged together b
- Adding exams, homework, etc.: users will be able to add any tasks they need to do as reminders. Those can be associated with events that either have been downloaded from the user's Stud.IP schedule, or ones the user has created manually. An overview will be provided that allows the user to have, well, an overview of everything they need to do.
- Finding your room: I'd like to add a feature that makes finding a specific room on the university's campus much easier than it is now. The university website does have a feature that, in theory, should do exactly that, but it works very poorly and does not list all rooms. My idea would be that the user could type in a room, the app could then make suggestions on which room the user may want to find based on the input, and then show the user a floor plan with the room highlighted.
- This could prove quite difficult though, as I do not have access to the university's proper floor plans. My best bet would be to either use the ones avalable online to create my own ones, or draw ones from scratch. As for finding all the rooms, I may have to walk around the university and find all rooms myself.
- Optimisations to data handling will be made. The app currently only supports storing a single timetable, which will be changed in future releases. A planned implementation will see the user being able to download schedules as well as create ones from scratch, with the schedules able to be selected via a dropdown menu.
- Some improvements to the Play Store screenshots are sorely needed...
## Why Android-only?
@@ -164,6 +164,12 @@ class AppRepository(app: Application) {
fun insertTimetable(timetable: Timetable) {
dao.insertTimetable(timetable)
/*
* require-non-null can be done here as we're adding an entry to this table beforehand,
* which guarantees that the element will not be null
*/
setPreferenceSelectedTimetable(dao.getLargestTimetableId()!!)
}
fun updateTimetable(timetable: Timetable) { dao.updateTimetable(timetable) }
@@ -174,7 +180,10 @@ class AppRepository(app: Application) {
fun getTimetableForId(id: Int): Timetable = dao.getTimetableForId(id)
fun deleteTimetable(id: Int) { dao.deleteTimetable(id) }
fun deleteTimetable(id: Int) {
dao.deleteTimetable(id)
setPreferenceSelectedTimetable(dao.getLargestTimetableId() ?: 0)
}
/**
* Retrieves all canteen offer date objects.
@@ -56,6 +56,9 @@ class EventFragment : AppFragment<FragmentEventBinding>() {
}
}
// Set the title to be selected so it scrolls (marquee)
binding.appTitleBar.isSelected = true
// Get localised weekday names
weekdays = context.resources?.getStringArray(R.array.weekdays) ?: arrayOf()
@@ -113,6 +116,12 @@ class EventFragment : AppFragment<FragmentEventBinding>() {
// Set visibility of the fetch button depending on whether any events have been stored
viewModel.getEventCount().observe(viewLifecycleOwner) { eventCount ->
val timetableName = viewModel.getSelectedTimetableName()
if (timetableName.isNotBlank()) {
binding.appTitleBar.text = timetableName
}
binding.buttonFetchSchedule.visibility = if (eventCount == 0) {
View.VISIBLE
} else {
@@ -27,6 +27,7 @@ import com.denizk0461.weserplaner.databinding.FragmentSettingsBinding
import com.denizk0461.weserplaner.model.FormattedDate
import com.denizk0461.weserplaner.model.Timetable
import com.denizk0461.weserplaner.sheet.AllergenConfigSheet
import com.denizk0461.weserplaner.sheet.TimetableAddSheet
import com.denizk0461.weserplaner.sheet.TimetableEditSheet
import com.denizk0461.weserplaner.values.AppLayout
import com.denizk0461.weserplaner.viewmodel.SettingsViewModel
@@ -112,6 +113,10 @@ class SettingsFragment : AppFragment<FragmentSettingsBinding>() {
binding.autoCompleteTimetable.setText(timetableAdapter.getItem(position), false)
}
binding.buttonAddTimetable.setOnClickListener {
openBottomSheet(TimetableAddSheet())
}
binding.buttonEditTimetable.setOnClickListener {
if (binding.autoCompleteTimetable.text.isBlank()) {
context.theme.showErrorSnackBar(
@@ -12,6 +12,6 @@ import androidx.room.PrimaryKey
*/
@Entity(tableName = "timetables")
data class Timetable(
@PrimaryKey(autoGenerate = true) val id: Int,
@PrimaryKey(autoGenerate = true) val id: Int = 0,
val name: String,
)
@@ -0,0 +1,51 @@
package com.denizk0461.weserplaner.sheet
import android.os.Bundle
import android.view.View
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.NavHostFragment
import com.denizk0461.weserplaner.R
import com.denizk0461.weserplaner.data.viewBinding
import com.denizk0461.weserplaner.databinding.SheetTimetableAddBinding
import com.denizk0461.weserplaner.fragment.SettingsFragment
import com.denizk0461.weserplaner.model.Timetable
import com.denizk0461.weserplaner.viewmodel.SheetTimetableAddViewModel
class TimetableAddSheet : AppSheet(R.layout.sheet_timetable_add) {
// View binding
private val binding: SheetTimetableAddBinding by viewBinding(SheetTimetableAddBinding::bind)
// View model
private val viewModel: SheetTimetableAddViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Set up FAB behaviour for parent fragment's FAB if activity is not null
val parentFragment = activity?.let { activity ->
// Retrieve parent fragment to access its functions
val navHostFragment = activity
.supportFragmentManager
.fragments[0] as NavHostFragment
navHostFragment
.childFragmentManager
.primaryNavigationFragment as SettingsFragment
}
binding.buttonCancel.setOnClickListener {
dismiss()
}
binding.buttonSave.setOnClickListener {
viewModel.insertTimetable(Timetable(name = binding.editTextTitle.text.toString().trim()))
// Tell the user that the event has been updated
parentFragment?.showSnackBar(
getString(R.string.settings_timetable_sheet_confirm_add)
)
dismiss()
}
}
}
@@ -9,7 +9,7 @@ import com.denizk0461.weserplaner.values.AppLayout
*
* @param app reference to the app
*/
class EventViewModel(app: Application) : AppViewModel(app) {
class EventViewModel(private val app: Application) : AppViewModel(app) {
/**
* Retrieves the number of Stud.IP events available in the database.
@@ -18,6 +18,14 @@ class EventViewModel(app: Application) : AppViewModel(app) {
*/
fun getEventCount(): LiveData<Int> = repo.getEventCount()
fun getSelectedTimetableName(): String = returnBlocking {
try {
repo.getTimetableForId(repo.getPreferenceSelectedTimetable()).name
} catch (e: NullPointerException) {
""
}
}
/**
* This value determines whether the user wants their timetable to launch with the current day.
*/
@@ -0,0 +1,12 @@
package com.denizk0461.weserplaner.viewmodel
import android.app.Application
import androidx.lifecycle.viewModelScope
import com.denizk0461.weserplaner.model.Timetable
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class SheetTimetableAddViewModel(application: Application) : AppViewModel(application) {
fun insertTimetable(timetable: Timetable) { viewModelScope.launch(Dispatchers.IO) { repo.insertTimetable(timetable) }}
}
+41 -31
View File
@@ -144,45 +144,55 @@
android:text="@string/settings_selected_timetable_header"
android:textSize="16sp" />
<androidx.appcompat.widget.LinearLayoutCompat
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/text_layout_timetable"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
android:hint="@string/settings_selected_timetable_hint"
app:boxStrokeColor="@color/selector_text_input"
app:hintTextColor="@color/selector_text_input">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/text_layout_timetable"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
android:layout_width="0dp"
<com.google.android.material.textfield.MaterialAutoCompleteTextView
android:id="@+id/auto_complete_timetable"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginEnd="8dp"
android:hint="@string/settings_selected_timetable_hint"
app:boxStrokeColor="@color/selector_text_input"
app:hintTextColor="@color/selector_text_input">
android:textColor="?attr/colorOnSurfaceVariant"
android:lines="1"
android:maxLines="1"
tools:text="asdf asdf asdf asdf asdf ??? asdf asdf asdf asdf asdf asdf asdf asdf asdf"
android:inputType="text"
android:clickable="false"
android:focusable="false"/>
<com.google.android.material.textfield.MaterialAutoCompleteTextView
android:id="@+id/auto_complete_timetable"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="?attr/colorOnSurfaceVariant"
android:inputType="none"/>
</com.google.android.material.textfield.TextInputLayout>
</com.google.android.material.textfield.TextInputLayout>
</androidx.appcompat.widget.LinearLayoutCompat>
<com.google.android.material.button.MaterialButton
style="@style/Widget.Material3.Button.IconButton.Outlined"
android:id="@+id/button_edit_timetable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="0dp"
app:icon="@drawable/edit"
app:iconPadding="0dp"
app:strokeColor="?attr/colorOutlineVariant"
app:iconTint="?attr/colorOnSurfaceVariant"
tools:ignore="UnusedAttribute"
android:tooltipText="@string/settings_selected_timetable_edit_button_hint"/>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingHorizontal="16dp">
</androidx.appcompat.widget.LinearLayoutCompat>
<com.google.android.material.button.MaterialButton
style="@style/Widget.Material3.Button.OutlinedButton"
android:id="@+id/button_add_timetable"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/settings_timetable_button_add"
app:strokeColor="?attr/colorOutlineVariant"
android:layout_marginEnd="8dp"/>
<com.google.android.material.button.MaterialButton
style="@style/Widget.Material3.Button.OutlinedButton"
android:id="@+id/button_edit_timetable"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/settings_timetable_button_edit"
app:strokeColor="?attr/colorOutlineVariant"/>
</androidx.appcompat.widget.LinearLayoutCompat>
@@ -0,0 +1,108 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/sheet"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.bottomsheet.BottomSheetDragHandleView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<com.google.android.material.button.MaterialButton
style="@style/SheetCloseButton"
android:id="@+id/button_cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/sheet_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="22sp"
android:paddingHorizontal="24dp"
android:layout_marginBottom="8dp"
app:fontFamily="@font/lato_blackitalic"
android:text="@string/settings_timetable_sheet_header_add"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/button_cancel"/>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical">
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="16dp"
android:paddingHorizontal="24dp">
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/settings_timetable_add_hint"/>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:id="@+id/text_layout_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:hint="@string/settings_selected_timetable_hint"
app:boxStrokeColor="@color/selector_text_input"
app:hintTextColor="@color/selector_text_input">
<com.google.android.material.textfield.TextInputEditText
style="@style/ThemeOverlay.Material3.TextInputEditText.OutlinedBox"
android:textColor="?attr/colorOnSurfaceVariant"
android:id="@+id/edit_text_title"
android:inputType="text"
android:maxLines="1"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</com.google.android.material.textfield.TextInputLayout>
<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_marginVertical="8dp"
android:animateLayoutChanges="true">
<com.google.android.material.button.MaterialButton
android:id="@+id/button_save"
style="@style/Widget.Material3.Button.TonalButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/save"
app:icon="@drawable/save"/>
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.core.widget.NestedScrollView>
</androidx.appcompat.widget.LinearLayoutCompat>
@@ -70,6 +70,8 @@
style="@style/ThemeOverlay.Material3.TextInputEditText.OutlinedBox"
android:textColor="?attr/colorOnSurfaceVariant"
android:id="@+id/edit_text_title"
android:inputType="text"
android:maxLines="1"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
+6
View File
@@ -249,11 +249,17 @@
<string name="settings_selected_timetable_header">Wähle, welchen Stundenplan einzusehen</string>
<string name="settings_selected_timetable_hint">Stundenplan</string>
<string name="settings_selected_timetable_edit_button_hint">Stundenplan bearbeiten</string>
<string name="settings_selected_timetable_edit_text_title">Name des Stundenplans</string>
<string name="settings_timetable_button_add">Hinzufügen</string>
<string name="settings_timetable_button_edit">Bearbeiten</string>
<string name="settings_timetable_sheet_header">Diesen Stundenplan bearbeiten</string>
<string name="settings_timetable_sheet_header_add">Neuen Stundenplan hinzufügen</string>
<string name="settings_timetable_edit_fail">Es gibt keinen Stundenplan zum bearbeiten!</string>
<string name="settings_timetable_sheet_confirm_save">Stundenplan wurde aktualisiert.</string>
<string name="settings_timetable_sheet_confirm_add">Stundenplan wurde hinzugefügt.</string>
<string name="settings_timetable_sheet_confirm_delete">Stundenplan wurde gelöscht.</string>
<string name="settings_timetable_sheet_delete_confirm">Sicher?</string>
<string name="settings_timetable_add_hint">Hinweis: wenn du einen Stundenplan von Stud.IP herunterlädst, wird dieser automatisch als neuer Stundenplan hinzugefügt, also musst du in diesem Fall hier nichts machen!</string>
<string name="settings_current_day_title">Starte Stundenplan am heutigen Tag</string>
<string name="settings_current_day_subtitle">Ansonsten wird Montag beim Start angezeigt</string>
+6
View File
@@ -254,11 +254,17 @@
<string name="settings_selected_timetable_header">Select which timetable to display</string>
<string name="settings_selected_timetable_hint">Timetable</string>
<string name="settings_selected_timetable_edit_button_hint">Edit timetable</string>
<string name="settings_selected_timetable_edit_text_title">Timetable name</string>
<string name="settings_timetable_button_add">Add</string>
<string name="settings_timetable_button_edit">Edit</string>
<string name="settings_timetable_sheet_header">Edit this timetable</string>
<string name="settings_timetable_sheet_header_add">Add a new timetable</string>
<string name="settings_timetable_edit_fail">There is no timetable to edit!</string>
<string name="settings_timetable_sheet_confirm_save">Timetable has been updated.</string>
<string name="settings_timetable_sheet_confirm_add">Timetable has been added.</string>
<string name="settings_timetable_sheet_confirm_delete">Timetable has been deleted.</string>
<string name="settings_timetable_sheet_delete_confirm">Sure?</string>
<string name="settings_timetable_add_hint">Hint: if you download a timetable from Stud.IP, it automatically gets added as a new timetable, so you won\'t need to do anything here!</string>
<string name="settings_current_day_title">Launch timetable on current day</string>
<string name="settings_current_day_subtitle">If unselected, Monday will be shown on launch instead</string>
+2 -2
View File
@@ -1,6 +1,6 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id("com.android.application") version "8.0.2" apply false
id("com.android.library") version "8.0.2" apply false
id("com.android.application") version "8.1.0" apply false
id("com.android.library") version "8.1.0" apply false
id("org.jetbrains.kotlin.android") version "1.8.21" apply false
}