diff --git a/app/src/main/java/com/denizk0461/weserplaner/adapter/TaskOverviewAdapter.kt b/app/src/main/java/com/denizk0461/weserplaner/adapter/TaskOverviewAdapter.kt new file mode 100644 index 0000000..6bdae47 --- /dev/null +++ b/app/src/main/java/com/denizk0461/weserplaner/adapter/TaskOverviewAdapter.kt @@ -0,0 +1,108 @@ +package com.denizk0461.weserplaner.adapter + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import com.denizk0461.weserplaner.BuildConfig +import com.denizk0461.weserplaner.data.AppDiffUtilCallback +import com.denizk0461.weserplaner.databinding.ItemTaskBinding +import com.denizk0461.weserplaner.model.EventTask +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +class TaskOverviewAdapter( + private val onClickListener: OnClickListener, +) : RecyclerView.Adapter() { + + /** + * List of all events. + */ + private val tasks: MutableList = mutableListOf() + + /** + * View holder class for parent class + * + * @param binding view binding object + */ + class TaskViewHolder(val binding: ItemTaskBinding) : RecyclerView.ViewHolder(binding.root) + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TaskViewHolder { + return TaskViewHolder( + ItemTaskBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) + } + + // Set the amount of items to the size of the filtered list, so only events of the current day + override fun getItemCount(): Int = tasks.size + + override fun onBindViewHolder(holder: TaskViewHolder, position: Int) { + + // Retrieve item for current position + val currentItem = tasks[position] + + // Context for easier reference + val context = holder.binding.root.context + + // view set-up + SimpleDateFormat("yyyy-MM-dd, HH:mm:ss.SSS", Locale.GERMANY) + .format(Date(BuildConfig.BUILD_TIME_MILLIS)) + + // Set up single click listener + holder.binding.linearLayout.setOnClickListener { + onClickListener.onClick(currentItem) + } + + // Set up long press listener + holder.binding.linearLayout.setOnLongClickListener { + onClickListener.onLongClick(currentItem) + } + } + + /** + * Updates the data and calculates the difference between the old dataset and the newly provided + * dataset. + * + * @param newData new dataset to be displayed + */ + fun setNewData(newData: List) { + // Calculate the difference between the old list and the new list + val diffResult = DiffUtil.calculateDiff(AppDiffUtilCallback(tasks, newData)) + + // Remove all items from the list + tasks.clear() + + // Add all items from the new list + tasks.addAll(newData) + + // Tell the DiffUtil which items have changed between the two lists + diffResult.dispatchUpdatesTo(this) + } + + /** + * Interface used to evaluate clicks and long presses on a given item. + */ + interface OnClickListener { + + /** + * Executed when an item has been clicked. + * + * @param task item that has been clicked + */ + fun onClick(task: EventTask) + + + /** + * Executed when an item has been long-pressed. + * + * @param task item that has been long-pressed + * @return whether the long press was successful + */ + fun onLongClick(task: EventTask): Boolean + } +} \ No newline at end of file diff --git a/app/src/main/java/com/denizk0461/weserplaner/data/Extensions.kt b/app/src/main/java/com/denizk0461/weserplaner/data/Extensions.kt index 4688305..189585e 100644 --- a/app/src/main/java/com/denizk0461/weserplaner/data/Extensions.kt +++ b/app/src/main/java/com/denizk0461/weserplaner/data/Extensions.kt @@ -17,6 +17,9 @@ import com.denizk0461.weserplaner.exception.ParcelNotFoundException import com.denizk0461.weserplaner.model.TextSheetContentId import com.denizk0461.weserplaner.sheet.TextSheet import com.google.android.material.snackbar.Snackbar +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale import kotlin.jvm.Throws /* @@ -148,6 +151,19 @@ fun SwipeRefreshLayout.setRainbowProgressCircle() { ) } +/** + * Formats a date object to a string in the ISO 8601 format. Example: + * 2023-05-21, 14:32:59.067 + * + * @receiver date object to convert to string + * @param useHighPrecision determines whether seconds and milliseconds are shown + * @return date formatted as ISO 8601 string + */ +fun Date.formatToIso8601String(useHighPrecision: Boolean = false): String = SimpleDateFormat( + "yyyy-MM-dd, HH:mm${if (useHighPrecision) ":ss.SSS" else ""}", + Locale.GERMANY, +).format(this) + /** * Timeslots for which a conversion to an academic quarter is applicable. */ diff --git a/app/src/main/java/com/denizk0461/weserplaner/db/AppDAO.kt b/app/src/main/java/com/denizk0461/weserplaner/db/AppDAO.kt index fb221b1..4a3f7e8 100644 --- a/app/src/main/java/com/denizk0461/weserplaner/db/AppDAO.kt +++ b/app/src/main/java/com/denizk0461/weserplaner/db/AppDAO.kt @@ -65,6 +65,66 @@ interface AppDAO { @Query("DELETE FROM studip_events") fun nukeEvents() + /* --- event tasks --- */ + + /** + * Retrieve all event tasks ordered by their ID (order: date created). + * + * @return tasks ordered by date created + */ + @Query( + "SELECT event_tasks.taskId, " + + "event_tasks.eventId, " + + "event_tasks.dueDate, " + + "event_tasks.title AS taskTitle, " + + "studip_events.title AS eventTitle, " + + "studip_events.lecturer, " + + "event_tasks.notes, " + + "studip_events.room " + + "FROM event_tasks " + + "JOIN studip_events ON event_tasks.eventId = studip_events.eventId " + + "ORDER BY taskId" + ) + fun getAllTasksOrderDateCreated(): LiveData> + + /** + * Retrieve all event tasks ordered alphabetically by their title. + * + * @return tasks ordered by title + */ + @Query( + "SELECT event_tasks.taskId, " + + "event_tasks.eventId, " + + "event_tasks.dueDate, " + + "event_tasks.title AS taskTitle, " + + "studip_events.title AS eventTitle, " + + "studip_events.lecturer, " + + "event_tasks.notes, " + + "studip_events.room " + + "FROM event_tasks " + + "JOIN studip_events ON event_tasks.eventId = studip_events.eventId " + + "ORDER BY taskTitle") + fun getAllTasksOrderAlphabetically(): LiveData> + + /** + * Retrieve all event tasks ordered by their due date. + * + * @return tasks ordered by date due + */ + @Query( + "SELECT event_tasks.taskId, " + + "event_tasks.eventId, " + + "event_tasks.dueDate, " + + "event_tasks.title AS taskTitle, " + + "studip_events.title AS eventTitle, " + + "studip_events.lecturer, " + + "event_tasks.notes, " + + "studip_events.room " + + "FROM event_tasks " + + "JOIN studip_events ON event_tasks.eventId = studip_events.eventId " + + "ORDER BY dueDate") + fun getAllTasksOrderDateDue(): LiveData> + /* --- canteen offers --- */ /** diff --git a/app/src/main/java/com/denizk0461/weserplaner/db/AppRepository.kt b/app/src/main/java/com/denizk0461/weserplaner/db/AppRepository.kt index ace1c38..691fa26 100644 --- a/app/src/main/java/com/denizk0461/weserplaner/db/AppRepository.kt +++ b/app/src/main/java/com/denizk0461/weserplaner/db/AppRepository.kt @@ -80,6 +80,18 @@ class AppRepository(app: Application) { */ fun nukeEvents() { dao.nukeEvents() } + /** + * Retrieves tasks for all events ordered as specified. + * + * @param order order in which the tasks are returned + * @return tasks wrapped in a LiveData object + */ + fun getTasks(order: TaskOrder): LiveData> = when (order) { + TaskOrder.DATE_CREATED -> dao.getAllTasksOrderDateCreated() + TaskOrder.ALPHABETICALLY -> dao.getAllTasksOrderAlphabetically() + else -> dao.getAllTasksOrderDateDue() // TaskOrder.DATE_DUE + } + /** * Retrieves the canteen stored in the database. Should always be 0 or 1. * diff --git a/app/src/main/java/com/denizk0461/weserplaner/fragment/SettingsFragment.kt b/app/src/main/java/com/denizk0461/weserplaner/fragment/SettingsFragment.kt index 9369165..78d20f6 100644 --- a/app/src/main/java/com/denizk0461/weserplaner/fragment/SettingsFragment.kt +++ b/app/src/main/java/com/denizk0461/weserplaner/fragment/SettingsFragment.kt @@ -16,6 +16,7 @@ import com.denizk0461.weserplaner.BuildConfig import com.denizk0461.weserplaner.R import com.denizk0461.weserplaner.activity.FetcherActivity import com.denizk0461.weserplaner.activity.ImageActivity +import com.denizk0461.weserplaner.data.formatToIso8601String import com.denizk0461.weserplaner.data.getTextSheet import com.denizk0461.weserplaner.data.showSnackBar import com.denizk0461.weserplaner.data.showToast @@ -23,7 +24,6 @@ import com.denizk0461.weserplaner.databinding.FragmentSettingsBinding import com.denizk0461.weserplaner.sheet.AllergenConfigSheet import com.denizk0461.weserplaner.viewmodel.SettingsViewModel import java.nio.charset.StandardCharsets -import java.text.SimpleDateFormat import java.util.* /** @@ -318,8 +318,7 @@ class SettingsFragment : AppFragment() { // Set build version code and time text @SuppressLint("SetTextI18n") binding.buildTimeText.text = "v${BuildConfig.VERSION_CODE} | ${ - SimpleDateFormat("yyyy-MM-dd, HH:mm:ss.SSS", Locale.GERMANY) - .format(Date(BuildConfig.BUILD_TIME_MILLIS)) + Date(BuildConfig.BUILD_TIME_MILLIS).formatToIso8601String(useHighPrecision = true) }" // Set up switch for showing beta screens diff --git a/app/src/main/java/com/denizk0461/weserplaner/fragment/TaskOverviewFragment.kt b/app/src/main/java/com/denizk0461/weserplaner/fragment/TaskOverviewFragment.kt index 6c2c1eb..0eaabb5 100644 --- a/app/src/main/java/com/denizk0461/weserplaner/fragment/TaskOverviewFragment.kt +++ b/app/src/main/java/com/denizk0461/weserplaner/fragment/TaskOverviewFragment.kt @@ -6,19 +6,27 @@ import android.view.View import android.view.ViewGroup import androidx.appcompat.widget.PopupMenu import androidx.fragment.app.viewModels +import androidx.recyclerview.widget.LinearLayoutManager 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.TaskOrder import com.denizk0461.weserplaner.viewmodel.TaskOverviewViewModel /** * Fragment that shows the user an overview of their tasks and exams. */ -class TaskOverviewFragment : AppFragment() { +class TaskOverviewFragment : AppFragment(), + TaskOverviewAdapter.OnClickListener { // View model reference for providing access to the database private val viewModel: TaskOverviewViewModel by viewModels() + // Task adapter for the recycler view + private val taskAdapter: TaskOverviewAdapter = TaskOverviewAdapter(this) + // Instantiate the view binding override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { _binding = FragmentTaskOverviewBinding.inflate(inflater, container, false) @@ -38,8 +46,6 @@ class TaskOverviewFragment : AppFragment() { ) binding.buttonOrder.setOnClickListener { - - // Create menu for selecting a new canteen PopupMenu(binding.root.context, binding.buttonOrder).apply { setOnMenuItemClickListener { item -> @@ -60,6 +66,17 @@ class TaskOverviewFragment : AppFragment() { } } + binding.recyclerViewLayout.recyclerView.apply { + // Set page to scroll vertically + layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) + + // Assign adapter to this recycler view + adapter = taskAdapter + } + + // insert sample data TODO change to db + taskAdapter.setNewData(viewModel.getTasks(TaskOrder.DATE_DUE)) + // Set up scroll change listener to shrink and extend FAB accordingly binding.recyclerViewLayout.recyclerView.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY -> // Calculate the vertical scroll difference @@ -73,4 +90,13 @@ class TaskOverviewFragment : AppFragment() { } } } + + override fun onClick(task: EventTask) { + // TODO + } + + override fun onLongClick(task: EventTask): Boolean { + // TODO + return false + } } \ No newline at end of file diff --git a/app/src/main/java/com/denizk0461/weserplaner/model/EventTask.kt b/app/src/main/java/com/denizk0461/weserplaner/model/EventTask.kt index cc3b00a..200bad2 100644 --- a/app/src/main/java/com/denizk0461/weserplaner/model/EventTask.kt +++ b/app/src/main/java/com/denizk0461/weserplaner/model/EventTask.kt @@ -4,6 +4,16 @@ import androidx.room.Entity import androidx.room.ForeignKey import androidx.room.PrimaryKey +/** + * Database entity used to store tasks assigned to specific [StudIPEvent] elements. + * + * @param taskId primary key for the entity + * @param eventId foreign key referencing the event this task is assigned to + * @param dueDate time and date at which the task is due + * @param title title of the task + * @param notes user-added notes for the task + * @param room room the task may take place in + */ @Entity( tableName = "event_tasks", foreignKeys = [ diff --git a/app/src/main/java/com/denizk0461/weserplaner/model/EventTaskExtended.kt b/app/src/main/java/com/denizk0461/weserplaner/model/EventTaskExtended.kt new file mode 100644 index 0000000..72beca9 --- /dev/null +++ b/app/src/main/java/com/denizk0461/weserplaner/model/EventTaskExtended.kt @@ -0,0 +1,25 @@ +package com.denizk0461.weserplaner.model + +/** + * Extended representation of [com.denizk0461.weserplaner.model.EventTask] that includes important + * attributes from a given task's associated [com.denizk0461.weserplaner.model.StudIPEvent]. + * + * @param taskId primary key for the entity + * @param eventId foreign key referencing the event this task is assigned to + * @param dueDate time and date at which the task is due + * @param eventTitle title of the event + * @param taskTitle title of the task + * @param lecturer lecturer(s) organising the event + * @param notes user-added notes for the task + * @param room room the task may take place in + */ +data class EventTaskExtended( + val taskId: Int, + val eventId: Int, + val dueDate: Int, + val eventTitle: String, + val taskTitle: String, + val lecturer: String, + val notes: String, + val room: String, +) diff --git a/app/src/main/java/com/denizk0461/weserplaner/model/TaskOrder.kt b/app/src/main/java/com/denizk0461/weserplaner/model/TaskOrder.kt new file mode 100644 index 0000000..8bcae01 --- /dev/null +++ b/app/src/main/java/com/denizk0461/weserplaner/model/TaskOrder.kt @@ -0,0 +1,25 @@ +package com.denizk0461.weserplaner.model + +/** + * Used to determine the order in which tasks should be shown. + * + * @param key index by which to tell which order method to use + */ +enum class TaskOrder(key: Int) { + + /** + * Order tasks by date due. + */ + DATE_DUE(0), + + /** + * Order tasks by date created. + */ + DATE_CREATED(1), + + /** + * Order tasks alphabetically. + */ + ALPHABETICALLY(2), + ; +} \ No newline at end of file diff --git a/app/src/main/java/com/denizk0461/weserplaner/viewmodel/TaskOverviewViewModel.kt b/app/src/main/java/com/denizk0461/weserplaner/viewmodel/TaskOverviewViewModel.kt index e1fd259..4596023 100644 --- a/app/src/main/java/com/denizk0461/weserplaner/viewmodel/TaskOverviewViewModel.kt +++ b/app/src/main/java/com/denizk0461/weserplaner/viewmodel/TaskOverviewViewModel.kt @@ -1,11 +1,26 @@ package com.denizk0461.weserplaner.viewmodel import android.app.Application +import androidx.lifecycle.LiveData +import com.denizk0461.weserplaner.model.EventTask +import com.denizk0461.weserplaner.model.TaskOrder /** * View model class for [com.denizk0461.weserplaner.fragment.TaskOverviewFragment]. */ class TaskOverviewViewModel(application: Application) : AppViewModel(application) { + /** + * Retrieves tasks for all events ordered as specified. + * + * @param order order in which the tasks are returned + * @return tasks wrapped in a LiveData object + */ +// fun getTasks(order: TaskOrder): LiveData> = repo.getTasks(order) + fun getTasks(order: TaskOrder) = listOf( // dummy data + EventTask(0, 0, 1684758756, "title1", "user notes", "GW2 B-idk"), + EventTask(1, 0, 1684798756, "title2", "user notes", "GW3 C-idk"), + EventTask(2, 0, 1689758756, "title3", "user notes", "GW4 D-idk"), + ) } \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_task_overview.xml b/app/src/main/res/layout/fragment_task_overview.xml index ae143f8..9aff958 100644 --- a/app/src/main/res/layout/fragment_task_overview.xml +++ b/app/src/main/res/layout/fragment_task_overview.xml @@ -64,7 +64,8 @@ + android:layout_height="match_parent" + app:layout_behavior="@string/appbar_scrolling_view_behavior"/> -